GCS SDK for H5

本文为您介绍即时渲染功能的 H5 SDK 使用方式。

使用限制

浏览器依赖

推荐浏览器

操作系统

浏览器

支持的最低版本

Windows

Google Chrome

63

Mac

Google Chrome

63

Safari

11

Android

Google Chrome

63

微信内置浏览器

7.0.9(微信版本)

钉钉内置浏览器

11.2.5(钉钉版本)

华为浏览器

12.0.4

iOS

Google Chrome

63

Safari

11

微信内置浏览器

7.0.9(微信版本)

钉钉内置浏览器

11.2.5(钉钉版本)

浏览器用户行为保护策略(iOS端)

受限于浏览器用户行为保护策略,iOS端建议用户采用如下策略。

  1. 建议有用户交互后再打开声音,如果在无交互情况下调用setVolume将导致视频暂停。

  2. Safari 15.4及以上版本需要有用户交互后才支持自动播放。

  3. 低电量模式容易自动播放失败,需要有用户交互的同时调用replay接口。

对应用的要求

应用需要是Windows操作系统下,基于图形接口DirectX 11的exe可执行程序。​​

试验特性 - 兼容WebView引擎

可在原生应用的WebView浏览器引擎中运行,环境要求为:

  • iOS系统: 14.3及以上版本。

  • 安卓系统:Google Chrome主版本号79及以上版本。

开发说明​

说明
  • 对于本地开发调试,由于跨域限制:

    • 需要打开Chrome浏览器跨域模式

    • 需要为localhost绑定一个.aliyun.com结尾(如gcs.aliyun.com)的host,然后访问这个绑定的域名进行开发调试

  • 通过线上域名进行开发调试需要联系项目接口人添加相关域名白名单

快速开始

SDK引入方式

https://g.alicdn.com/aliyun-ecs/gcs-js-sdk/0.0.21/index.js

<script type="text/javascript" src="https://g.alicdn.com/aliyun-ecs/gcs-js-sdk/0.0.21/index.js"></script>

接口说明

接口

描述

init

初始化SDK,返回初始化结果。

prepare

开始资源调度

on

监听事件消息,如事件GCSEvent

start

启动应用

stop

停止应用,通知关停服务与串流

udpOpen

建立自定义数据通道

udpClose

关闭自定义数据通道

udpSend

自定义数据通道发送消息

setKeyBoard

自定义键盘

setSize

设置显示区域宽高

onStat

获取视频流参数

JoystickCreate

创建虚拟摇杆

JoystickOn

虚拟摇杆按钮添加监听事件

replay

重新拉流

setFullscreen

设置全屏

setBitrate

动态设置比特率

setVolume

设置音量

setMouse

设置鼠标输入

disconnect

关闭Rgc服务

典型调用顺序如下图所示。

image

创建 SDK 实例

const gcssdk = new GCSSDK();
gcssdk.version // 获取当前版本号

init

调用时机:实例化SDK后调用。用于初始化配置,返回初始化结果。

参数

类型

是否必填

备注

accessKey

string

应用公钥。

token

string

用户token(用于鉴权)。

更多信息,请参见生成token

说明

该参数默认5分钟失效。

userId

string

用户ID。

sessionId

string

唯一会话标识。

useLog

boolean

是否打印日志。

取值:

  • true:打印日志。

  • false(默认值):不打印。

gcssdk.init({
  accessKey,           // 应用公钥
  token,                  // 用户token
  userId,                 // 用户ID
  sessionId,             // 唯一会话标识
}).then(ready => {
  console.log(ready); // true
}).catch(e => {
  console.log(ready); // false
});

prepare

调用时机:init异步结果返回true时调用。

参数

类型

是否必填

备注

appId

string

应用ID。

appVersion

string

应用版本号。

container

object

承接应用的DOM容器。

appStartParam

string

应用启动命令,传给应用启动方的参数。

projectId

String

项目ID。当您设置该参数时,容器将只使用该项目下的资源运行应用,否则将自动选择任意可用资源。

idleTime

number

无操作超时提示时间。

取值范围:正整数。默认值:600。

单位:秒。

fillMode

number

缩放模式。

0: 不保证保持原有的比例,内容拉伸填充整个container。(默认值)

1: 保持原有比例,内容被缩放。

autoRotateContainer

boolean

默认值为false。

移动端场景下,当横竖屏切换时,是否基于container旋转适配。

gcssdk.prepare({
  appId, // 应用ID
  appVersion, // 应用版本号
  container, // Dom容器
  projectId, // 项目ID
  idleTime,
}).then(res => {
  console.log('plateformSessionId: ', res.plateformSessionId);
});

on

调用时机:实例化SDK后调用。监听SDK所有事件,根据消息下行处理相关逻辑。

gcssdk.on('GCSEvent', (res) => {
  const { type, code, message } = res;

  switch (code) {
    case '201010': // 应用运行环境准备完成
      console.log(message);
      break;
    case '902011': // 服务器连接成功
      console.log(message);
      break;
    case '902013': // 通道服务准备就绪
    case '902014': // 通道服务关闭
    default:
      break;
  }
});

off

调用时机:实例化SDK后调用。解除on方法挂载的事件监听

gcssdk.off('GCSEvent')

start

调用时机:在收到201010事件后调用。

gcssdk.start();

stop

调用时机:用户需要手动关闭应用时调用。

参数

类型

是否必填

备注

switchContainer

boolean

切换容器时需设置该参数为true。

gcssdk.stop();
gcssdk.stop({ switchContainer: true }); // 切换容器前关闭

udpOpen

调用时机:sdk建立连接后调用。用于建立自定义数据通道,且获得返回参数

参数

类型

是否必填

备注

port

string

端口号。

onMessage

Func

接收返回参数的函数。

function onMessage (evt) {
  console.log(`udpOnMessage: ${evt}`);
}

gcssdk.udpOpen(port, onMessage);

udpSend

调用时机:自定义数据通道建立后调用。用于发送信息。

参数

类型

是否必填

备注

msg

string

信息。

gcssdk.udpSend(msg);

udpClose

调用时机:自定义数据通道建立后调用。用于关闭自定义数据通道

gcssdk.udpClose()

setKeyBoard

参数

类型

是否必填

备注

keyCode

number

keycode 码。

isPressed

boolean

键盘是否按下。

取值:

  • true:按下键盘。

  • false(默认值):没有按下键盘。

// 键盘按下
document.onkeydown = function(event) {
  const e = event || window.event || arguments.callee.caller.arguments[0];
  e.preventDefault();

  gcssdk.setKeyBoard({
    keyCode: e.keyCode,
    isPressed: true
  });
};

setSize

参数

类型

是否必填

备注

width

number

DOM容器宽度。

height

number

DOM容器高度。

gcssdk.setSize(width, height);

setMouse

参数

类型

是否必填

备注

x

number

相对 x 坐标。

y

number

相对 y 坐标。

button

number

1 - 鼠标左键

2 - 鼠标中间键

3 - 鼠标右键

10 - 鼠标移动

isPressed

boolean

鼠标是否按下,默认false,即没有按下。

// 鼠标移动
video.onmousemove = function(event) {
  const e = event || window.event || arguments.callee.caller.arguments[0];
  e.preventDefault();
  const x = e.clientX;
  const y = e.clientY;

  gcssdk.setMouse({
    x, y,
    isPressed: false,
    button: 10
  });
};

onStat

获取视频流信息,在收到902011事件后调用。

参数

类型

是否必填

备注

onMessage

Func

接收返回参数的函数。

/**
 * 视频流参数stat
 * @param Mbps 
 * @param delay 
 * @param fps 
 * @param resolution 
 * @param rtt 
 */
const onMessage = (stat) => {
  console.log(stat)
}
gcssdk.onStat(onMessage);

JoystickCreate

创建虚拟摇杆,可在收到201010事件后调用。

参数

类型

是否必填

备注

joystickImage

obj

back

string

摇杆背景图片

front

string

按钮图片

frontPressed

string

按钮按下时图片

const params = {
  joystickImage: {
    back: 'url',
    front: 'base64'
  }
}
// 需保证gcs-sdk-videoBox挂载点存在
gcssdk.JoystickCreate(params);

JoystickOn

向虚拟摇杆按钮传入监听事件,在收到902012事件后调用。

参数

类型

是否必填

备注

type

string

监听事件类型,包含mousedown, touchstart, mousemove, touchmove, mouseup, touchend

function

Func

事件回调函数

const function = (e) => {
  console.log(e)
}
gcssdk.JoystickOn(type, function);

replay

重新拉流。

gcssdk.replay();

setFullscreen

设置全屏。

参数

类型

是否必填

备注

type

Number

全屏类型。

取值:

  • 0(默认值):不全屏。

  • 2:全屏。

containerId

object

需要全屏的DOM容器ID,请保证唯一性。

默认对video进行全屏。

// 开启全屏(建议按需使用contrainterId)
gcssdk.setFullscreen({
  type: 2,
});   

// 取消全屏  
gcssdk.setFullscreen({
  type: 0
});

setBitrate

动态设置比特率,在902012事件后调用。

参数

类型

是否必填

备注

kbps

number

kbps

取值: [300, 3000]

gcssdk.setBitrate(1000) // 启动时kbps默认值为3000

setVolume

设置音量。

参数

类型

是否必填

备注

volume

number

音量大小:0~1,默认为1。

gcssdk.setVolume(volume);

disconnect

关闭Rgc服务,在902011事件后调用。

gcssdk.disconnect()

事件说明

事件名

GCSEvent

事件映射表

EventType

EventCode

EventMessage

10

101030

找不到正确的accessKey

10

101040

请求服务超时

10

101050

用户token校验未通过

10

101099

请求服务异常

10

102010

绑定长连接服务失败

10

109010

当前浏览器不支持

10

109011

浏览器版本过低

10

109012

不支持webrtc

10

109013

不支持H264

10

109020

参数不合法

20

201010

应用运行环境准备完成

50

501010

内部错误

50

501020

调度失败

50

501030

资源包CU不足

50

501031

资源不足

50

501040

会话不存在

50

501041

启动会话请求被流控

50

501050

应用不存在

50

501051

应用未适配完成

50

501052

应用版本不存在

50

501053

应用停止中

50

501061

租户已欠费停服

50

501062

租户已欠费释放

50

501070

项目下没有该应用

50

502010

容器创建失败

50

502011

调度异常(ip/port为空)

50

502020

应用启动失败

50

502030

应用停止失败

60

603010

应用停止成功

90

901000

串流鉴权失败

90

901010

连接服务器用户鉴权失败

90

901011

连接服务器用户鉴权超时而断开

90

901012

服务端未收到用户token

90

901013

容器未分配token

90

901014

服务连接失败

90

901015

服务断开连接

90

901099

连接异常中断

90

902011

服务连接成功

90

902012

画面准备就绪(启动完毕)

90

902013

通道服务准备就绪

90

902014

通道服务关闭

90

903010

鼠标长时间未操作

90

904010

网络丢包

90

904011

网络抖动过大

90

904012

处理延迟过大

90

904013

网络延迟过大

90

904014

网络包重传

90

904015

连接异常

90

904016

重新连接中

90

904017

重新连接成功

90

904018

重新连接失败

示例代码

生成token

说明

调用接口前,您需配置环境变量,通过环境变量读取访问凭证。更多信息,请参见使用说明

云渲染的userIdtenantIdsecretKey的环境变量名:USER_IDTENANT_IDSECRET_KEY

Java示例

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

/**
 * 字符串加密解密工具类
 */
public class DesUtil {

    private static final Map<String, Cipher> ENCRYPT_MAP = new HashMap<>();

    public static void main(String[] args) {
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。建议您创建并使用RAM用户进行API访问或日常运维。
        // 此处以把AccessKey ID和AccessKeySecret保存在环境变量为例说明。您也可以根据业务需要,保存到配置文件里。
        // 建议不要把AccessKey ID和AccessKeySecret保存到代码里,会存在密钥泄漏风险。
        // 用户ID: 您自定义的ID 
        String userId = System.getenv("USER_ID");
        // 租户ID: 请填写您的阿里云账号ID
        Long tenantId = Long.valueOf(System.getenv("TENANT_ID"));
        // secretKey: 阿里云提供
        String secretKey = System.getenv("SECRET_KEY");
        System.out.println(token);
    }

    public static String encrypt(String userId, Long tenantId,  String secretKey) {
        String originStr = String.format("%s_%s_%s", userId, tenantId, System.currentTimeMillis());
        try {
            byte[] array = initEncryptCipher(secretKey).doFinal(originStr.getBytes(StandardCharsets.UTF_8));
            return byteArr2HexStr(array);
        } catch (Exception e) {
            throw new RuntimeException("failed to encrypt. originStr=" + originStr, e);
        }
    }

    /**
     * 将byte数组转换为表示16进制值的字符串
     *
     * @param arrB 需要转换的byte数组
     * @return 转换后的字符串
     */
    public static String byteArr2HexStr(byte[] arrB) {
        int iLen = arrB.length;
        // 每个byte用两个字符才能表示,所以字符串的长度是数组长度的两倍
        StringBuilder sb = new StringBuilder(iLen * 2);
        for (byte anArrB : arrB) {
            int intTmp = anArrB;
            // 把负数转换为正数
            while (intTmp < 0) {
                intTmp = intTmp + 256;
            }
            // 小于0F的数需要在前面补0
            if (intTmp < 16) {
                sb.append("0");
            }
            sb.append(Integer.toString(intTmp, 16));
        }
        return sb.toString();
    }

    /**
     * 从指定字符串生成密钥,密钥所需的字节数组长度为8位,不足8位时后面补0,超出8位只取前8位
     *
     * @param tmp 构成该字符串的字节数组
     * @return 生成的密钥
     */
    private static Key getKey(byte[] tmp) {
        // 创建一个空的8位字节数组(默认值为0)
        byte[] arrB = new byte[8];

        // 将原始字节数组转换为8位
        for (int i = 0; i < tmp.length && i < arrB.length; i++) {
            arrB[i] = tmp[i];
        }
        return new javax.crypto.spec.SecretKeySpec(arrB, "DES");
    }

    private static Cipher initEncryptCipher(String secretKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
        Cipher encryptCipher = ENCRYPT_MAP.get(secretKey);
        if (encryptCipher == null) {
            encryptCipher = Cipher.getInstance("DES");
            ENCRYPT_MAP.put(secretKey, encryptCipher);
        }
        Key key = getKey(secretKey.getBytes(StandardCharsets.UTF_8));
        encryptCipher.init(Cipher.ENCRYPT_MODE, key);
        return encryptCipher;
    }
}

Node.js示例

var CryptoJS = require("crypto-js");

/**
 * 使用DES加密后生成鉴权token
 * @param {string} userId 自定义用户ID
 * @param {string} tenantId 阿里云账号ID
 * @param {string} secretKey 阿里云提供
 * @returns token
 */
function encrypt(userId, tenantId, secretKey) {
  const message = `${userId}_${tenantId}_${new Date().getTime()}`;
  const key = CryptoJS.enc.Utf8.parse(secretKey);
  
  const encrypted = CryptoJS.DES.encrypt(message, key, { 
    mode: CryptoJS.mode.ECB, 
    padding: CryptoJS.pad.Pkcs7 
  });
  return encrypted.ciphertext.toString();
}

const token = encrypt(userId, tenantId, secretKey);
console.log(token);

启动串流

说明

调用接口前,您需配置环境变量,通过环境变量读取访问凭证。更多信息,请参见身份验证配置

云渲染的AccessKey IDAccessKey Secret的环境变量名:ALIBABA_CLOUD_ACCESS_KEY_IDALIBABA_CLOUD_ACCESS_KEY_SECRET

Node.js示例

const initConfig = {
  credential: new Credential(),
  token: 'xxx',
  userId: 'xxx',
  sessionId: 'xxx',
}

const prepareConfig = {
  appId: 'xxx',
  appVersion: 'xxx',
  container: document.querySelector('#renderDom'),
  // appStartParam: '',
}

const gcssdk = new GCSSDK();

gcssdk.init(initConfig).then(ready => {
  gcssdk.prepare(prepareConfig);
}).catch(e => {
  console.log('GCS init failed.')
})

gcssdk.on('GCSEvent', res => {
  const { type, code, message } = res;
  console.log(`onGCSEvent: ${type} ${code} ${message}`);
  switch(code) {
    case '201010':
      gcssdk.start(); // 启动游戏
      break;
    case '401010':  	// 接收应用的数据
      console.log(message);
      break;
    default:
      break;
  }
})

常见问题

  1. sessionIdCustomSessionId的作用是什么?

    云渲染服务中出现的CustomSessionId等于接口init中的sessionId

    • 如果只允许用户单开,即一个用户只能开启一个会话,sessionId可以与userId保持一致。如果前后两个页面的sessionId一致,第二个页面会把第一个页面踢出。

    • 如果允许用户多开,sessionId设置随机数即可。

  2. CustomSessionIdPlatformSessionId的区别是什么?

    CustomSessionId是用户提供的。PlatformSessionId是由GCS服务生成的,用于错误追踪,如果链路出错可通过该信息排查。

  3. prepare方法中container容器指的是前端Js dom容器节点吗?

    是的,是用于承接video的容器。SDK会在这个容器下创建应用渲染所需的video

  4. 什么时候可以获取video?

    监听到902012事件video挂载成功。

  5. 如果用户非主动退出会话,会话资源什么时候会被释放掉?

    三分钟无响应后台会关闭应用。