本文為您介紹即時渲染功能的 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端建議用戶采用如下策略。
建議有用戶交互后再打開聲音,如果在無交互情況下調用
setVolume
將導致視頻暫停。Safari 15.4及以上版本需要有用戶交互后才支持自動播放。
低電量模式容易自動播放失敗,需要有用戶交互的同時調用
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服務 |
典型調用順序如下圖所示。
const gcssdk = new GCSSDK();
gcssdk.version // 獲取當前版本號
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
});
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 | 否 | 鍵盤是否按下。 取值:
|
// 鍵盤按下
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 | 是 | 全屏類型。 取值:
|
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
調用接口前,您需配置環境變量,通過環境變量讀取訪問憑證。更多信息,請參見使用說明。
云渲染的userId
、tenantId
、secretKey
的環境變量名:USER_ID
、TENANT_ID
、SECRET_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 ID
和AccessKey Secret
的環境變量名:ALIBABA_CLOUD_ACCESS_KEY_ID
和ALIBABA_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;
}
})
常見問題
sessionId
與CustomSessionId
的作用是什么?云渲染服務中出現的
CustomSessionId
等于接口init
中的sessionId
:如果只允許用戶單開,即一個用戶只能開啟一個會話,
sessionId
可以與userId
保持一致。如果前后兩個頁面的sessionId
一致,第二個頁面會把第一個頁面踢出。如果允許用戶多開,
sessionId
設置隨機數即可。
CustomSessionId
與PlatformSessionId
的區別是什么?CustomSessionId
是用戶提供的。PlatformSessionId
是由GCS服務生成的,用于錯誤追蹤,如果鏈路出錯可通過該信息排查。prepare
方法中container
容器指的是前端Js dom
容器節點嗎?是的,是用于承接
video
的容器。SDK會在這個容器下創建應用渲染所需的video
。什么時候可以獲取
video
?監聽到902012事件時
video
掛載成功。如果用戶非主動退出會話,會話資源什么時候會被釋放掉?
三分鐘無響應后臺會關閉應用。