即时渲染功能的H5 SDK。
1. 使用限制
1.1 浏览器依赖
1.1.1 推荐浏览器
操作系统 |
浏览器 |
支持的最低版本 |
Windows |
Chrome |
63 |
Mac |
Chrome |
63 |
Safari |
11 |
|
Android |
Chrome |
63 |
微信内置浏览器。 |
7.0.9(微信版本) |
|
钉钉内置浏览器。 |
11.2.5(钉钉版本) |
|
HUAWEI自带浏览器。 |
12.0.4 |
|
iOS |
Chrome |
63 |
Safari |
11 |
|
微信内置浏览器。 |
7.0.9(微信版本) |
|
钉钉内置浏览器。 |
11.2.5(钉钉版本) |
1.1.2 浏览器用户行为保护策略(iOS端)
受限于浏览器用户行为保护策略,iOS端建议用户采用如下策略:
-
建议有用户交互后再打开声音,如果在无交互情况下调用
setVolume
将导致视频暂停。 -
Safari15.4及以上版本需要有用户交互后才支持自动播放。
-
低电量模式容易自动播放失败,需要有用户交互的同时调用
replay
接口。
1.2 对应用的要求
应用需要是Windows操作系统下,基于图形接口DirectX11的exe可执行程序。
1.3 域名加白
使用本SDK,均需配置跨域,请联系项目接口人加白名单。
本地调试请打开Chrome跨域模式。
1.4 试验特性 - 兼容WebView引擎
可在原生应用的WebView浏览器引擎中运行,环境要求为:
-
iOS系统: 14.3及以上版本。
-
安卓系统:Chrome主版本号79及以上版本。
2. 快速开始
SDK引入方式: https://g.alicdn.com/cgcs-fe/gcs-h5-sdk/2.2.6/index.js 。
关于如何实现与渲染程序之间的数据交流,请参见 CGProxy SDK使用示例 。
3. 接口说明
接口 |
描述 |
最低版本支持 |
init |
初始化SDK,返回初始化结果。 |
2.0.1 |
prepare |
开始资源调度。 |
2.0.1 |
start |
启动应用。 |
2.0.1 |
stop |
停止应用,通知关闭服务与串流。 |
2.0.1 |
on |
监听事件消息,如事件GCSEvent。 |
2.0.1 |
setSize |
设置显示区域宽高。 |
2.0.1 |
setVolume |
设置音量。 |
2.0.1 |
setMouse |
设置鼠标输入。 |
2.1.5 |
setKeyBoard |
设置键盘输入。 |
2.1.5 |
setFullscreen |
设置全屏。 |
2.1.13 |
sendDataToService |
直传数据给应用。 |
2.1.2 |
replay |
播放重试。 |
2.1.14 |
sendRawEvent |
发送操控指令输入。 |
2.1.18 |
isSupport |
检测SDK是否可用。 |
2.2.1 |
setStreamProfile |
设置码流参数。 |
2.2.2 |
setOrientation |
设置画面旋转角度。 |
2.2.6 |
典型调用顺序如下图所示。
3.1 init
调用时机:实例化SDK后调用。用于初始化配置,返回初始化结果。
参数 |
类型 |
是否必填 |
备注 |
accessKey |
string |
是 |
应用公钥。 |
token |
string |
是 |
用户token(用于鉴权)。 更多信息,请参见 生成token 。
说明
该参数默认5分钟失效。 |
userId |
string |
是 |
用户ID。 |
sessionId |
string |
是 |
唯一会话标识。 |
useLog |
boolean |
否 |
是否打印日志。 取值:
|
gcssdk.init({
accessKey, // 应用公钥
token, // 用户token
userId, // 用户ID
sessionId, // 唯一会话标识
}).then(ready => {
console.log(ready); // true
}).catch(e => {
console.log(ready); // false
});
3.2 prepare
调用时机:init异步结果返回 true 时调用。
参数 |
类型 |
是否必填 |
备注 |
appId |
string |
是 |
应用ID。 |
appVersion |
string |
否 |
应用版本号。 |
container |
object |
是 |
承接应用的DOM容器。 |
appStartParam |
string |
否 |
应用启动命令,传给应用启动方的参数。 |
projectId |
String |
否 |
项目ID。当您设置该参数时,容器将只使用该项目下的资源运行应用,否则将自动选择任意可用资源。 |
width |
number |
否 |
DOM容器宽度。 |
height |
number |
否 |
DOM容器高度。 |
keymouseMode |
number |
否 |
键鼠支持方式,取值:
|
isMouseShow |
number |
否 |
是否显示鼠标。 取值:
|
idleTime |
number |
否 |
无操作超时时间,超时会自动停止应用。 取值范围:10~3600。默认值:600。 单位:秒。
说明
-1表示长时间无操作不会自动退出,请谨慎操作。 |
gcssdk.prepare({
appId, // 应用ID
appStartParam, // 应用启动命令,与用户具体业务相关
container, // Dom容器
});
3.3 start
调用时机:在收到201010事件后调用。
gcssdk.start();
3.4 stop
调用时机:用户需要手动关闭应用时调用。
gcssdk.stop();
3.5 setSize
参数 |
类型 |
是否必填 |
备注 |
width |
number |
是 |
DOM容器宽度。 |
height |
number |
是 |
DOM容器高度。 |
gcssdk.setSize(width, height);
3.6 setVolume
参数 |
类型 |
是否必填 |
备注 |
volume |
number |
是 |
音量大小:0~1,默认为1。 |
gcssdk.setVolume(volume);
3.7 on
调用时机:实例化SDK后调用。监听SDK所有事件,根据消息下行处理相关逻辑。
gcssdk.on('GCSEvent', res => {
const { type, code, message } = res;
console.log(`onGCSEvent: ${type} ${code} ${message}`);
switch(code) {
case '401010': // 应用回传数据
console.log(message);
break;
case '401020': // 串流实时数据
const {
width, // 分辨率宽度
height, // 分辨率高度
fps, // 帧率
bandwidth, // 带宽(bps)
rtt // 网络时延(ms)
} = message;
break;
case '402010': // 终端节点数据
const {
isp, // 供应商 [TELECOM(中国电信)、UNICOM(中国联通)、MOBILE(中国移动)、BGP]
district, // 大区 [华东、华北、华南、华中、西南]
} = message;
break;
default:
break;
});
3.8 setMouse
参数 |
类型 |
是否必填 |
备注 |
x |
number |
是 |
相对x坐标。 |
y |
number |
是 |
相对y坐标。 |
button |
number |
是 |
取值:
|
isPressed |
boolean |
否 |
鼠标是否按下,默认false,即没有按下。 |
left |
number |
否 |
video元素的左坐标。 |
top |
number |
否 |
video元素的上坐标。 |
width |
number |
否 |
video元素的宽。 |
height |
number |
否 |
video元素的高。 |
// 鼠标移动
video.onmousemove = function(event) {
const e = event || window.event || arguments.callee.caller.arguments[0];
e.preventDefault();
// const left = video.getBoundingClientRect().left;
// const top = video.getBoundingClientRect().top;
// const width = video.offsetWidth;
// const height = video.offsetHeight;
const x = e.clientX;
const y = e.clientY;
gcssdk.setMouse({
// left, top,
// width, height,
x, y,
isPressed: false,
button: 10
};
3.9 setKeyBoard
参数 |
类型 |
是否必填 |
备注 |
keyCode |
number |
是 |
键盘类型。 |
isPressed |
boolean |
否 |
键盘是否按下。 取值:
|
// 键盘按下
document.onkeydown = function(event) {
const e = event || window.event || arguments.callee.caller.arguments[0];
e.preventDefault();
gcssdk.setKeyBoard({
keyCode: e.keyCode,
isPressed: true
};
3.10 setFullscreen
开启全屏并兼容iOS屏幕自动旋转。
参数 |
类型 |
是否必填 |
备注 |
type |
Number |
是 |
全屏类型。 取值:
|
contrainterId |
object |
否 |
需要全屏的DOM容器ID,请保证唯一性。 默认对video进行全屏。 |
// 开启全屏并兼容IOS屏幕自动旋转(建议按需使用contrainterId)
gcssdk.setFullscreen({
type: 2,
contrainterId: 'content'
// 取消全屏
gcssdk.setFullscreen({
type: 0
});
3.11 sendDataToService
直传数据给应用,根据业务需要使用。
参数 |
类型 |
是否必填 |
备注 |
data |
Uint8Array |
是 |
传递给应用的数据。 |
const _U8Afrom = Uint8Array.from.bind(Uint8Array);
function stringToUint8Array(str) {
return _U8Afrom(str, c => c.charCodeAt(0));
const uintData = stringToUint8Array(JSON.stringify(object));
gcssdk.sendDataToService(uintData); // 向应用传递数据
gcssdk.on('GCSEvent', res => {
const { type, code, message } = res;
switch(code) {
case '401010': // 接收应用的回调数据:此处返回的message为Uint8Array类型
// 为避免uint8ArrayToString的精度损失,该方法仅适用于[0-128]字节字符串
const callbackMsg = new TextDecoder().decode(message);
console.log(callbackMsg);
break;
default:
break;
})
3.12 replay
建议监听到「901080 自动播放失败」后,触发用户交互的同时进行调用。
如iOS低电量模式下自动播放失败,可使用此功能解决:
document.getElementById("confirmPlay").onclick = function() {
gcssdk.replay();
};
3.13 sendRawEvent
通过不同事件发送操控指令输入
sendRawEvent(eventConfig)
。
eventConfig
对象结构如下表:
事件 |
事件类型 |
eventConfig |
参数说明 |
备注 |
手柄事件 |
手柄连接 |
{ type: "apppadconnect" } |
无 |
手柄初始化后继续调用其他事件。 |
手柄断开 |
{ type: "apppaddisconnect" } |
无 |
调用该事件使手柄断开。 |
|
手柄摇杆 |
{ type: "apppadstick", side: "left"/"right", x: [-32767~32767], y: [-32767~32767] } |
|
无 |
|
手柄触发 |
{ type: "apppadtrigger", side: "left"/"right", force: [0, 255] } |
|
L2/LT或R2/RT。 |
|
手柄按键 |
{ type: "apppadbutton", bitMask, isPressed: true/false } |
|
当按键组合时,传递多个
|
当手柄事件类型为
apppadbutton
时,bitMask的值如下表所示:
bitMask |
说明 |
|
1 |
0x0001 |
方向键(向上)。 |
2 |
0x0002 |
方向键(向下)。 |
4 |
0x0004 |
方向键(向左)。 |
8 |
0x0008 |
方向键(向右)。 |
16 |
0x0010 |
开始。 |
32 |
0x0020 |
选择或返回。 |
64 |
0x0040 |
左摇杆垂直按下(L3/LS)。 |
128 |
0x0080 |
右摇杆垂直按下(R3/RS)。 |
256 |
0x0100 |
左触发键(L1/LB)。 |
512 |
0x0200 |
右触发键(R1/RB)。 |
4096 |
0x1000 |
A键。 |
8192 |
0x2000 |
B键。 |
16384 |
0x4000 |
X键。 |
32768 |
0x8000 |
Y键。 |
当您同时按键组合时,传递多个bitMask值之和。
例如,同时按A键和B键:bitMask=4096+8192=12288。
eventConfig:
sendRawEvent({ type: "apppadbutton", bitMask: 12288, isPressed: true })
。
3.14 isSupport
检测SDK是否可用。
gcssdk.isSupport().then(res => {
// 可用
}).catch(err => {
console.log('GCSSDK not support: ', err)
// { type: '10', code: '109010', message: '当前浏览器不支持' }
// { type: '10', code: '109011', message: '浏览器版本过低' }
// { type: '10', code: '109012', message: '不支持webrtc' }
// { type: '10', code: '109013', message: '不支持H264' }
})
3.15 setStreamProfile
设置码流参数后,云端会根据实际情况动态调整参数信息。您可以通过401020事件监听码流设置后的实时数据。
参数 |
类型 |
是否必填 |
备注 |
config |
number |
是 |
码流参数对象。 |
|- maxBitrate |
number |
否 |
最大码率。 单位:Mbps。 取值为整数[1,15]。 |
|- minBitrate |
number |
否 |
最小码率。 单位:Mbps。 取值为整数[1,15]。 |
gcssdk.setStreamProfile({
minBitrate: 13,
maxBitrate: 15
})
3.16 setOrientation
设置画面基于左上角的旋转角度。
本接口不建议和setFullscreen接口同时使用,否则画面旋转角度会出现问题。
请求参数 |
类型 |
是否必填 |
备注 |
container |
string |
是 |
旋转目标:
|
deg |
number |
是 |
基于左上角的旋转角度。取值为0或9。 |
返回参数 |
类型 |
备注 |
success |
boolean |
是否成功设置画面基于左上角的旋转角度。 取值:
|
code |
string |
返回字段。 取值:
|
const { success, code } = gcssdk.setOrientation({
container: 'coordinate',
deg: 90
});
4. 事件说明
事件名
GCSEvent
事件映射表
EventType |
EventCode |
EventMessage |
开始支持版本 |
10 |
101030 |
找不到正确的accessKey。 |
2.0.1 |
10 |
101040 |
请求服务超时。 |
2.0.1 |
10 |
101050 |
用户token校验未通过。 |
2.0.1 |
10 |
101099 |
请求服务异常。 |
2.0.1 |
10 |
102010 |
绑定长连接服务失败。 |
2.0.1 |
10 |
109010 |
当前浏览器不支持。 |
2.0.1 |
10 |
109011 |
浏览器版本过低。 |
2.2.1 |
10 |
109012 |
不支持webrtc。 |
2.2.1 |
10 |
109013 |
不支持H264。 |
2.2.1 |
10 |
109020 |
参数不合法。 |
2.0.1 |
20 |
201010 |
应用运行环境准备完成。 |
2.0.1 |
40 |
401010 |
Uint8Array(应用回传数据)。 |
2.1.2 |
40 |
401020 |
object(串流实时数据)。 |
2.1.10 |
40 |
402010 |
object(终端节点数据)。 |
2.1.10 |
50 |
501010 |
内部错误。 |
2.0.1 |
50 |
501020 |
调度失败。 |
2.0.1 |
50 |
501030 |
资源包CU不足。 |
2.0.1 |
50 |
501031 |
资源不足。 |
2.1.10 |
50 |
501040 |
会话不存在。 |
2.0.1 |
50 |
501041 |
启动会话请求被流控。 |
2.1.4 |
50 |
501050 |
应用不存在。 |
2.0.1 |
50 |
501051 |
应用未适配完成。 |
2.0.1 |
50 |
501052 |
应用版本不存在。 |
2.0.1 |
50 |
501053 |
应用停止中。 |
2.0.1 |
50 |
501061 |
租户已欠费。 |
2.1.3 |
50 |
501062 |
租户已欠费释放。 |
2.1.3 |
50 |
502010 |
容器创建失败。 |
2.0.1 |
50 |
502011 |
调度异常(ip/port为空)。 |
2.0.1 |
50 |
502020 |
应用启动失败。 |
2.0.1 |
50 |
502030 |
应用停止失败。 |
2.0.1 |
60 |
603010 |
应用停止成功。 |
2.1.13 |
90 |
901000 |
串流鉴权失败。 |
2.2.0 |
90 |
901010 |
连接服务器用户鉴权失败。 |
2.0.1 |
90 |
901080 |
自动播放失败。 |
2.1.14 |
90 |
901011 |
连接服务器用户鉴权超时而断开。 |
2.0.1 |
90 |
901012 |
服务端未收到用户token。 |
2.0.1 |
90 |
901013 |
容器未分配token。 |
2.0.1 |
90 |
901014 |
连接服务器失败。 |
2.0.1 |
90 |
901099 |
连接异常中断。 |
2.0.1 |
90 |
902010 |
服务器启动鉴权成功。 |
2.0.1 |
90 |
902011 |
服务器连接成功。 |
2.0.1 |
90 |
902012 |
画面准备就绪(启动完毕)。 |
2.0.1 |
90 |
902013 |
通道服务准备就绪。 |
2.2.4 |
90 |
903010 |
因长时间未操作导致踢出。 |
2.0.1 |
90 |
903020 |
因账户在其它设备登录而被踢出。 |
2.0.1 |
90 |
904010 |
网络断开,进入重连状态。 |
2.0.1 |
90 |
904020 |
重连成功。 |
2.0.1 |
90 |
904030 |
重连失败。 |
2.0.1 |
5. 示例代码
5.1 生成token
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) {
// 用户ID: 您自定义的ID
String userId = "XXXX";
// 租户ID: 请填写您的阿里云账号ID
Long tenantId = XXXX;
// secretKey: 阿里云提供
String secretKey = "MjKXXXX27dtR";
// 生成token
String token = encrypt(userId, tenantId, secretKey);
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);
5.2 启动串流
const initConfig = {
accessKey: 'xxx',
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;
})
6. 常见问题
-
sessionId
与CustomSessionId的作用是什么?GCS服务中出现的CustomSessionId等于接口
init
中的sessionId
:-
如果只允许用户单开,即一个用户只能开启一个会话,
sessionId
可以与userId
保持一致。如果前后两个页面的sessionId
一致,第二个页面会把第一个页面踢出。 -
如果允许用户多开,
sessionId
设置随机数即可。
-
-
CustomSessionId与PlatformSessionId的区别是什么?
CustomSessionId是用户提供的。PlatformSessionId是由GCS服务生成的,用于错误追踪,如果链路出错可通过该信息排查。
-
prepare
方法中container
容器指的是前端Js dom
容器节点吗?是的,是用于承接
video
的容器。SDK会在这个容器下创建应用渲染所需的video
。 -
什么时候可以获取
video
?监听到902010事件时
video
挂载成功。 -
如果用户非主动退出会话,会话资源什么时候会被释放掉?
三分钟无响应后台会关闭应用。