阿里云ARTC Web SDK是由阿里云提供的一套基于Web的實時通信開發工具。它允許開發者在Web應用中快速集成高質量的音視頻通話功能、實時消息傳遞等實時互動功能。本文為您演示快速搭建屬于自己的ARTC應用。
步驟一:開通應用
步驟二:獲取應用ID和AppKey
成功開通應用后,在應用管理列表中找到該應用,單擊操作欄中的管理按鈕,進入基本信息頁面,在該頁面獲取對應的應用ID和AppKey 。
步驟三:集成接入
集成SDK。
Script集成
在您的HTML頁面引入SDK腳本。
<script src="https://g.alicdn.com/apsara-media-box/imp-web-rtc/6.12.3/aliyun-rtc-sdk.js"></script>
NPM集成
在您的項目中使用npm安裝SDK。
npm install aliyun-rtc-sdk --save
初始化引擎。
// 以下兩種引入方式二選一 // 當以 npm 包方式引入時執行 import AliRtcEngine from 'aliyun-rtc-sdk'; // 當以 Script 方式引入時執行 const AliRtcEngine = window.AliRtcEngine; // 檢測環境 const checkResult = await AliRtcEngine.isSupported(); if (!checkResult.support) { // 當前環境不支持使用,提示用戶更換或升級瀏覽器 } // 創建引擎實例,可以保存至全局變量中 const aliRtcEngine = AliRtcEngine.getInstance();
創建好AliRtcEngine實例后,需要監聽、處理相關事件。
// 當前用戶離開頻道 aliRtcEngine.on('bye', (code) => { // code 為原因碼,具體含義請查看 API 文檔 console.log(`bye, code=${code}`); // 這里做您的處理業務,如退出通話頁面等 }); // 監聽遠端用戶上線 aliRtcEngine.on('remoteUserOnLineNotify', (userId, elapsed) => { console.log(`用戶 ${userId} 加入頻道,耗時 ${elapsed} 秒`); // 這里處理您的業務邏輯,如展示這個用戶的模塊 }); // 監聽遠端用戶下線 aliRtcEngine.on('remoteUserOffLineNotify', (userId, reason) => { // reason 為原因碼,具體含義請查看 API 文檔 console.log(`用戶 ${userId} 離開頻道,原因碼: ${reason}`); // 這里處理您的業務邏輯,如銷毀這個用戶的模塊 }); // 監聽遠端流訂閱變化 aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => { // oldState、newState 類型均為AliRtcSubscribeState,值包含 0(初始化)、1(未訂閱)、2(訂閱中)、3(已訂閱) // interval 為兩個狀態之間的變化時間間隔,單位毫秒 console.log(`頻道 ${channelId} 遠端用戶 ${userId} 訂閱狀態由 ${oldState} 變為 ${newState}`); // 這里處理觀看遠端流的邏輯 // 當 newState 變為 3 時可以通過 setRemoteViewConfig 播放遠端流 // 當 newState 變為 1 時可以停止播放 }); // 監聽鑒權信息過期 aliRtcEngine.on('authInfoExpired', () => { // 該回調觸發代表鑒權信息已過期 // 需要重新獲取 token 等數據,調用 refreshAuthInfo 接口更新鑒權數據 aliRtcEngine.refreshAuthInfo({ userId, token, timestamp }); }); // 監聽用戶鑒權信息即將過期 aliRtcEngine.on('authInfoWillExpire', () => { // 該回調在鑒權信息30秒前觸發,收到該回調后應該及時更新鑒權信息 // 若想要繼續在會中,需要重新獲取 token 等數據,調用 joinChannel 重新入會 });
(可選)設置頻道模式,默認通話模式。詳細信息,請參見設置頻道模式和用戶角色。
// 設置頻道模式,支持傳入字符串 communication(通話模式)、interactive_live(互動模式) aliRtcEngine.setChannelProfile('interactive_live'); // 設置角色,互動模式時調用才生效 // 支持傳入字符串 interactive(互動角色,允許推拉流)、live(觀眾角色,僅允許拉流) aliRtcEngine.setClientRole('interactive');
加入頻道。Token計算方式參見Token鑒權,您可以按需選擇單參數入會或者多參數入會的方案。
單參數入會
const userName = '測試用戶1'; // 可以修改為您的用戶名(支持中文) try { // fetchToken需要您來實現,從服務端獲取Base64Token const base64Token = await fetchToken(); await aliRtcEngine.joinChannel(base64Token, userName); // 加入成功,繼續執行其他操作 } catch (error) { // 加入失敗 }
多參數入會
// 參考文檔 Token 鑒權部分,從服務端或本地生成鑒權信息 // 注意:為了您的數據安全,任何情況下都不應該將帶有 appKey 的 token 計算邏輯發布給用戶 const appId = 'yourAppId'; // 從控制臺獲取 const appKey = 'yourAppKey'; // 從控制臺獲取,注意請勿在生產環境露出您的 AppKey const channelId = 'AliRtcDemo'; // 可以修改為您的頻道ID(僅支持英文字母、數字) const userId = 'test1'; // 可以修改為您的用戶ID(僅支持英文字母、數字) const userName = '測試用戶1'; // 可以修改為您的用戶名(支持中文) const timestamp = Math.floor(Date.now() / 1000) + 3600; // 一個小時后過期 try { const token = await generateToken(appId, appKey, channelId, userId, timestamp); // 加入頻道,參數 token、nonce 等一般由服務端返回 await aliRtcEngine.joinChannel({ channelId, userId, appId, token, timestamp, }, userName); // 加入成功,繼續執行其他操作 } catch (error) { // 加入失敗 }
預覽畫面和推流。默認情況下,加入頻道后將自動采集本地的音視頻數據并推送至阿里云GRTN網絡中。您可以參考以下方式預覽您的本地畫面。
在HTML代碼中增加一個
id
為localPreviewer
的VIDEO元素。<video id="localPreviewer" muted style="display: block;width: 320px;height: 180px;background-color: black;" ></video>
通過
setLocalViewConfig
方法傳入元素ID開啟預覽。// 第一個參數支持傳入 HTMLVideoElement 或對應的元素 ID,傳入 null 時停止預覽 // 第二個參數支持傳入 1 (預覽相機流)、2(預覽屏幕共享流) aliRtcEngine.setLocalViewConfig('localPreviewer', 1);
訂閱遠端音視頻流。默認情況下,加入頻道后將自動訂閱其他主播用戶的音視頻流,若有音頻流將自動播放,需要觀看相機流、屏幕流,則需要通過
setRemoteViewConfig
接口開啟。在HTML代碼中增加一個
id
為remoteVideoContainer
的DIV元素作為容器。<div id="remoteVideoContainer"></div>
監聽到遠端視頻流訂閱變化后,若已訂閱
setRemoteViewConfig
接口播放,若未訂閱則移除。// 存儲 Video 元素 const remoteVideoElMap = {}; // 遠端容器元素 const remoteVideoContainer = document.querySelector('#remoteVideoContainer'); function removeRemoteVideo(userId) { const el = remoteVideoElMap[userId]; if (el) { aliRtcEngine.setRemoteViewConfig(null, userId, 1); el.pause(); remoteVideoContainer.removeChild(el); delete remoteVideoElMap[userId]; } } // 同第二步監聽事件示例代碼中 videoSubscribeStateChanged 示例 aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => { // oldState、newState 類型均為AliRtcSubscribeState,值包含 0(初始化)、1(未訂閱)、2(訂閱中)、3(已訂閱) // interval 為兩個狀態之間的變化時間間隔,單位毫秒 console.log(`頻道 ${channelId} 遠端用戶 ${userId} 訂閱狀態由 ${oldState} 變為 ${newState}`); // 處理示例 if (newState === 3) { const video = document.createElement('video'); video.autoplay = true; video.setAttribute('style', 'display: block;width: 320px;height: 180px;background-color: black;'); remoteVideoElMap[userId] = video; remoteVideoContainer.appendChild(video); // 第一個參數傳入 HTMLVideoElement // 第二個參數傳入遠端用戶 ID // 第三個參數支持傳入 1 (預覽相機流)、2(預覽屏幕共享流) aliRtcEngine.setRemoteViewConfig(video, userId, 1); } else if (newState === 1) { removeRemoteVideo(userId); } });
結束流程。
// 停止本地預覽 await aliRtcEngine.stopPreview(); // 離開頻道 await aliRtcEngine.leaveChannel(); // 銷毀實例 aliRtcEngine.destroy();
快速體驗
前提條件
快速體驗demo需要您的開發環境啟動一個HTTP服務,如果您沒有安裝http-server
的npm包,可以先執行npm install --global http-server
指令全局安裝。
步驟一:創建目錄
創建一個demo文件夾,并按照下方文件目錄結構創建好quick.html
、quick.js
兩個文件。
- demo
- quick.html
- quick.js
步驟二:編輯quick.html
將下方代碼復制到quick.html,并保存。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>aliyun-rtc-sdk quick start</title>
<link rel="stylesheet" />
<style>
.video {
display: inline-block;
width: 320px;
height: 180px;
margin-right: 8px;
margin-bottom: 8px;
background-color: black;
}
</style>
</head>
<body class="container p-2">
<h1>aliyun-rtc-sdk 快速開始</h1>
<div class="toast-container position-fixed top-0 end-0 p-3">
<div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">登錄消息</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body" id="loginToastBody"></div>
</div>
<div id="onlineToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">用戶上線</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body" id="onlineToastBody"></div>
</div>
<div id="offlineToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">用戶下線</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body" id="offlineToastBody"></div>
</div>
</div>
<div class="row mt-3">
<div class="col-6">
<form id="loginForm">
<div class="form-group mb-2">
<label for="channelId" class="form-label">頻道號</label>
<input class="form-control" id="channelId" />
</div>
<div class="form-group mb-2">
<label for="userId" class="form-label">用戶ID</label>
<input class="form-control" id="userId" />
</div>
<button id="joinBtn" type="submit" class="btn btn-primary mb-2">加入頻道</button>
<button id="leaveBtn" type="button" class="btn btn-secondary mb-2" disabled>離開頻道</button>
</form>
<div class="mt-3">
<h4>本地預覽</h4>
<video
id="localPreviewer"
muted
class="video"
></video>
</div>
</div>
<div class="col-6">
<h4>遠端用戶</h4>
<div id="remoteVideoContainer"></div>
</div>
</div>
<script src="https://g.alicdn.com/code/lib/jquery/3.7.1/jquery.min.js"></script>
<script src="https://g.alicdn.com/code/lib/bootstrap/5.3.0/js/bootstrap.min.js"></script>
<script src="https://g.alicdn.com/apsara-media-box/imp-web-rtc/6.12.3/aliyun-rtc-sdk.js"></script>
<script src="./quick.js"></script>
</body>
</html>
步驟三:編輯quick.js
將下方代碼復制到quick.js,并將應用ID和AppKey粘貼至代碼指定變量中保存。
function hex(buffer) {
const hexCodes = [];
const view = new DataView(buffer);
for (let i = 0; i < view.byteLength; i += 4) {
const value = view.getUint32(i);
const stringValue = value.toString(16);
const padding = '00000000';
const paddedValue = (padding + stringValue).slice(-padding.length);
hexCodes.push(paddedValue);
}
return hexCodes.join('');
}
async function generateToken(appId, appKey, channelId, userId, timestamp) {
const encoder = new TextEncoder();
const data = encoder.encode(`${appId}${appKey}${channelId}${userId}${timestamp}`);
const hash = await crypto.subtle.digest('SHA-256', data);
return hex(hash);
}
function showToast(baseId, message) {
$(`#${baseId}Body`).text(message);
const toast = new bootstrap.Toast($(`#${baseId}`));
toast.show();
}
// 填入您的應用ID 和 AppKey
const appId = '';
const appKey = '';
AliRtcEngine.setLogLevel(0);
let aliRtcEngine;
const remoteVideoElMap = {};
const remoteVideoContainer = document.querySelector('#remoteVideoContainer');
function removeRemoteVideo(userId, type = 'camera') {
const vid = `${type}_${userId}`;
const el = remoteVideoElMap[vid];
if (el) {
aliRtcEngine.setRemoteViewConfig(null, userId, type === 'camera' ? 1: 2);
el.pause();
remoteVideoContainer.removeChild(el);
delete remoteVideoElMap[vid];
}
}
function listenEvents() {
if (!aliRtcEngine) {
return;
}
// 監聽遠端用戶上線
aliRtcEngine.on('remoteUserOnLineNotify', (userId, elapsed) => {
console.log(`用戶 ${userId} 加入頻道,耗時 ${elapsed} 秒`);
// 這里處理您的業務邏輯,如展示這個用戶的模塊
showToast('onlineToast', `用戶 ${userId} 上線`);
});
// 監聽遠端用戶下線
aliRtcEngine.on('remoteUserOffLineNotify', (userId, reason) => {
// reason 為原因碼,具體含義請查看 API 文檔
console.log(`用戶 ${userId} 離開頻道,原因碼: ${reason}`);
// 這里處理您的業務邏輯,如銷毀這個用戶的模塊
showToast('offlineToast', `用戶 ${userId} 下線`);
removeRemoteVideo(userId, 'camera');
removeRemoteVideo(userId, 'screen');
});
aliRtcEngine.on('bye', code => {
// code 為原因碼,具體含義請查看 API 文檔
console.log(`bye, code=${code}`);
// 這里做您的處理業務,如退出通話頁面等
showToast('loginToast', `您已離開頻道,原因碼: ${code}`);
});
aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
// oldState、newState 類型均為AliRtcSubscribeState,值包含 0(初始化)、1(未訂閱)、2(訂閱中)、3(已訂閱)
// interval 為兩個狀態之間的變化時間間隔,單位毫秒
console.log(`頻道 ${channelId} 遠端用戶 ${userId} 訂閱狀態由 ${oldState} 變為 ${newState}`);
const vid = `camera_${userId}`;
// 處理示例
if (newState === 3) {
const video = document.createElement('video');
video.autoplay = true;
video.className = 'video';
remoteVideoElMap[vid] = video;
remoteVideoContainer.appendChild(video);
// 第一個參數傳入 HTMLVideoElement
// 第二個參數傳入遠端用戶 ID
// 第三個參數支持傳入 1 (預覽相機流)、2(預覽屏幕共享流)
aliRtcEngine.setRemoteViewConfig(video, userId, 1);
} else if (newState === 1) {
removeRemoteVideo(userId, 'camera');
}
});
aliRtcEngine.on('screenShareSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
// oldState、newState 類型均為AliRtcSubscribeState,值包含 0(初始化)、1(未訂閱)、2(訂閱中)、3(已訂閱)
// interval 為兩個狀態之間的變化時間間隔,單位毫秒
console.log(`頻道 ${channelId} 遠端用戶 ${userId} 屏幕流的訂閱狀態由 ${oldState} 變為 ${newState}`);
const vid = `screen_${userId}`;
// 處理示例
if (newState === 3) {
const video = document.createElement('video');
video.autoplay = true;
video.className = 'video';
remoteVideoElMap[vid] = video;
remoteVideoContainer.appendChild(video);
// 第一個參數傳入 HTMLVideoElement
// 第二個參數傳入遠端用戶 ID
// 第三個參數支持傳入 1 (預覽相機流)、2(預覽屏幕共享流)
aliRtcEngine.setRemoteViewConfig(video, userId, 2);
} else if (newState === 1) {
removeRemoteVideo(userId, 'screen');
}
});
}
$('#loginForm').submit(async e => {
// 防止表單默認提交動作
e.preventDefault();
const channelId = $('#channelId').val();
const userId = $('#userId').val();
const timestamp = Math.floor(Date.now() / 1000) + 3600 * 3;
if (!channelId || !userId) {
showToast('loginToast', '數據不完整');
return;
}
aliRtcEngine = AliRtcEngine.getInstance();
listenEvents();
try {
const token = await generateToken(appId, appKey, channelId, userId, timestamp);
// 設置頻道模式,支持傳入字符串 communication(通話模式)、interactive_live(互動模式)
aliRtcEngine.setChannelProfile('communication');
// 設置角色,互動模式時調用才生效
// 支持傳入字符串 interactive(互動角色,允許推拉流)、live(觀眾角色,僅允許拉流)
// aliRtcEngine.setClientRole('interactive');
// 加入頻道,參數 token、nonce 等一般有服務端返回
await aliRtcEngine.joinChannel(
{
channelId,
userId,
appId,
token,
timestamp,
},
userId
);
showToast('loginToast', '加入頻道成功');
$('#joinBtn').prop('disabled', true);
$('#leaveBtn').prop('disabled', false);
// 預覽
aliRtcEngine.setLocalViewConfig('localPreviewer', 1);
} catch (error) {
console.log('加入頻道失敗', error);
showToast('loginToast', '加入頻道失敗');
}
});
$('#leaveBtn').click(async () => {
Object.keys(remoteVideoElMap).forEach(vid => {
const arr = vid.split('_');
removeRemoteVideo(arr[1], arr[0]);
});
// 停止本地預覽
await aliRtcEngine.stopPreview();
// 離開頻道
await aliRtcEngine.leaveChannel();
// 銷毀實例
aliRtcEngine.destroy();
aliRtcEngine = undefined;
$('#joinBtn').prop('disabled', false);
$('#leaveBtn').prop('disabled', true);
showToast('loginToast', '已離開頻道');
});
步驟四:運行體驗
在終端中進入demo文件夾,然后執行
http-server -p 8080
,啟動一個HTTP服務。瀏覽器中新建標簽頁,訪問
localhost:8080/quick.html
,在界面上填入頻道ID和用戶ID ,單擊加入頻道按鈕。瀏覽器中再新建一個標簽頁,訪問
localhost:8080/quick.html
,在界面上填入與上一步相同的頻道ID和另一個用戶ID ,單擊加入頻道按鈕。界面上將自動訂閱另一個用戶的媒體流。