1 WebRTC音視頻通話功能簡介 本文介紹如何基於WebRTC快速實現一個簡單的實時音視頻通話。 在開始之前,您可以先瞭解一些實時音視頻推拉流相關的基礎概念: 流:一組按指定編碼格式封裝的音視頻數據內容。一個流可以包含幾個軌道,比如視頻和音頻軌道。 推流:把採集階段封包好的音視頻數據流推送到 ZE ...
1 WebRTC音視頻通話功能簡介
本文介紹如何基於WebRTC快速實現一個簡單的實時音視頻通話。
在開始之前,您可以先瞭解一些實時音視頻推拉流相關的基礎概念:
- 流:一組按指定編碼格式封裝的音視頻數據內容。一個流可以包含幾個軌道,比如視頻和音頻軌道。
- 推流:把採集階段封包好的音視頻數據流推送到 ZEGO 實時音視頻雲的過程。
- 拉流:從 ZEGO 實時音視頻雲將已有音視頻數據流拉取播放的過程。
- 房間:是 ZEGO 提供的音視頻空間服務,用於組織用戶群,同一房間內的用戶可以互相收發實時音視頻及消息。
- 用戶需要先登錄某個房間,才能進行音視頻推流、拉流操作。
- 用戶只能收到自己所在房間內的相關消息(用戶進出、音視頻流變化等)。
更多相關概念可參考即構官網關於音視頻SDK的介紹 術語說明。
2 實現WebRTC視頻通話的前提條件
在實現基本的WebRTC實時音視頻功能之前,請確保:
- 已在項目中集成 ZEGO Express SDK,詳情請參考 快速開始 - 集成。
- 已在 ZEGO 控制台 創建項目,申請有效的 AppID 和 ServerSecret,詳情請參考 控制台 - 項目管理 中的“項目信息”。
3 WebRTC音視頻通話示例代碼
我們提供了一個實現了WebRTC音視頻通話基本流程的完整示例 HTML 文件,可作為WebRTC開發過程中的參考。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Zego Express Video Call</title>
<!-- 此處需要改成正確的 SDK 版本號 -->
<script src="ZegoExpressWebRTC-x.x.x.js"></script>
<style type="text/css">
h1,
h4 {
text-align: center;
}
.video-wrapper {
width: 610px;
margin: 0 auto;
}
.video-wrapper h4 {
width: 300px;
display: inline-block;
position: relative;
}
#remote-video, #local-video {
width: 300px;
height: 270px;
display: inline-block;
position: relative;
}
.video-wrapper video {
height: auto;
}
</style>
</head>
<body>
<h1>
Zego RTC Video Call
</h1>
<div class="video-wrapper">
<h4>Local video</h4>
<h4>Remote video</h4>
<div id="local-video"></div>
<div id="remote-video"></div>
</div>
<script>
// 文檔中的 js 示例代碼可粘貼至此處
// 項目唯一標識 AppID,Number 類型,請從 ZEGO 控制台獲取
let appID = 0
// 接入伺服器地址 Server,String 類型,請從 ZEGO 控制台獲取(獲取方式請參考上文“前提條件”)
let server = ""
// 初始化實例
const zg = new ZegoExpressEngine(appID, server);
zg.setDebugVerbose(false)
// 房間狀態更新回調
// 此處在登錄房間成功後,立即進行推流。在實現具體業務時,您可選擇其他時機進行推流,只要保證當前房間連接狀態是連接成功的即可。
// 房間狀態更新回調
zg.on('roomStateChanged', async (roomID, reason, errorCode, extendedData) => {
if (reason == 'LOGINED') {
console.log("與房間連接成功,只有當房間狀態是連接成功時,才能進行推流、拉流等操作。")
}
})
zg.on('roomUserUpdate', (roomID, updateType, userList) => {
// 其他用戶進出房間的通知
});
zg.on('roomStreamUpdate', async (roomID, updateType, streamList, extendedData) => {
// 房間內其他用戶音視頻流變化的通知
if (updateType == 'ADD') {
// 流新增,開始拉流
// 此處演示拉取流新增的列表中第一條流的音視頻
const streamID = streamList[0].streamID;
// streamList 中有對應流的 streamID
const remoteStream = await zg.startPlayingStream(streamID);
// 創建媒體流播放組件
const remoteView = zg.createRemoteStreamView(remoteStream);
remoteView.play("remote-video", {enableAutoplayDialog:true});
} else if (updateType == 'DELETE') {
// 流刪除,通過流刪除列表 streamList 中每個流的 streamID 進行停止拉流。
const streamID = streamList[0].streamID;
zg.stopPlayingStream(streamID)
}
});
// 登錄房間,成功則返回 true
// userUpdate 設置為 true 才能收到 roomUserUpdate 回調。
let userID = "user1"; // userID 用戶自己設置,必須保證全局唯一
let userName = "user1";// userName 用戶自己設置,沒有唯一性要求
let roomID = "123"; // roomID 用戶自己設置,必須保證全局唯一
// token 由用戶自己的服務端生成,為了更快跑通流程,可以通過即構控制台 https://console.zego.im/ 獲取臨時的音視頻 token,token 為字元串
let token = ``;
zg.loginRoom(roomID, token, { userID, userName: userID }, { userUpdate: true }).then(async result => {
if (result == true) {
console.log("login success");
// 與房間連接成功,只有當房間狀態是連接成功時,才能進行推流、拉流等操作。
// 創建流、預覽
// 調用 createStream 介面後,需要等待 ZEGO 伺服器返迴流媒體對象才能執行後續操作
const localStream = await zg.createStream();
// 創建媒體流播放組件
const localView = zg.createLocalStreamView(localStream);
localView.play("local-video", {enableAutoplayDialog:true});
// 開始推流,將自己的音視頻流推送到 ZEGO 音視頻雲,此處 streamID 由用戶定義,需全局唯一
let streamID = new Date().getTime().toString();
zg.startPublishingStream(streamID, localStream)
}
});
// // 登錄房間的第二種寫法
// (async function main(){
// await zg.loginRoom(roomID, token, { userID, userName: userID }, { userUpdate: true })
// })()
</script>
</body>
</html>
4 WebRTC音視頻通話實現流程
以用戶 A 拉取用戶 B 的流為例,一次簡單的WebRTC實時音視頻通話主要流程如下:
- 用戶 A 創建實例,登錄房間。(登錄成功後,可預覽自己的畫面並推流。)
- 用戶 B 創建實例,登錄同一個房間。登錄成功後,用戶 B 開始推流,此時 SDK 會觸發
roomStreamUpdate
回調,表示房間內有流的變化。 - 用戶 A 可通過監聽
roomStreamUpdate
回調,當回調通知有流新增時,獲取用戶 B 的流 ID,來拉取播放用戶 B 剛剛推送的流。
4.1 創建WebRTC實時音視頻通話界面
為方便實現基本的WebRTC實時音視頻功能,您可參考WebRTC實時音視頻的示例代碼和下圖實現一個簡單實時音視頻功能的頁面。
打開或新建 “index.html” 頁面文件,並拷貝以下代碼到文件中。
<html>
<head>
<meta charset="UTF-8">
<title>Zego Express Video Call</title>
<style type="text/css">
* {
font-family: sans-serif;
}
h1,
h4 {
text-align: center;
}
#local-video, #remote-video {
width: 400px;
height: 300px;
border: 1px solid #dfdfdf;
}
#local-video {
position: relative;
margin: 0 auto;
display: block;
}
#remote-video {
display: flex;
margin: auto;
position: relative !important;
}
</style>
</head>
<body>
<h1>
Zego RTC Video Call
</h1>
<h4>Local video</h4>
<div id="local-video"></div>
<h4>Remote video</h4>
<div id="remote-video"></div>
<script>
// 文檔中的 js 示例代碼可粘貼至此處
// const zg = new ZegoExpressEngine(appID, server);
</script>
</body>
</html>
4.2 創建引擎並監聽回調
-
創建並初始化一個
ZegoExpressEngine
的實例,將您項目的 AppID 傳入參數 “appID”,Server 傳入參數 “server”。 -
即構實時音視頻SDK 提供如房間連接狀態、音視頻流變化、用戶進出等通知回調。為避免錯過任何通知,您需要在創建 ZegoExpressEngine 後立即監聽回調。
// 項目唯一標識 AppID,Number 類型,請從 ZEGO 控制台獲取
let appID = ;
// 接入伺服器地址 Server,String 類型,請從 ZEGO 控制台獲取(獲取方式請參考上文“前提條件”)
let server = "";
// 初始化實例
const zg = new ZegoExpressEngine(appID, server);
// 房間狀態更新回調
zg.on('roomStateChanged', (roomID, reason, errorCode, extendData) => {
if (reason == 'LOGINING') {
// 登錄中
} else if (reason == 'LOGINED') {
// 登錄成功
//只有當房間狀態是登錄成功或重連成功時,推流(startPublishingStream)、拉流(startPlayingStream)才能正常收發音視頻
//將自己的音視頻流推送到 ZEGO 音視頻雲
} else if (reason == 'LOGIN_FAILED') {
// 登錄失敗
} else if (reason == 'RECONNECTING') {
// 重連中
} else if (reason == 'RECONNECTED') {
// 重連成功
} else if (reason == 'RECONNECT_FAILED') {
// 重連失敗
} else if (reason == 'KICKOUT') {
// 被踢出房間
} else if (reason == 'LOGOUT') {
// 登出成功
} else if (reason == 'LOGOUT_FAILED') {
// 登出失敗
}
});
//房間內其他用戶進出房間的通知
//只有調用 loginRoom 登錄房間時傳入 ZegoRoomConfig,且 ZegoRoomConfig 的 userUpdate 參數為 “true” 時,用戶才能收到 roomUserUpdate回調。
zg.on('roomUserUpdate', (roomID, updateType, userList) => {
if (updateType == 'ADD') {
for (var i = 0; i < userList.length; i++) {
console.log(userList[i]['userID'], '加入了房間:', roomID)
}
} else if (updateType == 'DELETE') {
for (var i = 0; i < userList.length; i++) {
console.log(userList[i]['userID'], '退出了房間:', roomID)
}
}
});
zg.on('roomStreamUpdate', async (roomID, updateType, streamList, extendedData) => {
// 房間內其他用戶音視頻流變化的通知
});
4.3 檢測瀏覽器WebRTC相容性
考慮到不同的瀏覽器對 WebRTC 的相容性不同,在實現實時音視頻推拉流功能之前,您需要檢測瀏覽器能否正常運行 WebRTC。
您可以調用 checkSystemRequirements
介面檢測瀏覽器的相容性,檢測結果的含義,請參考 ZegoCapabilityDetection
介面下的參數描述。
const result = await zg.checkSystemRequirements();
// 返回的 result 為相容性檢測結果。 webRTC 為 true 時表示支持 webRTC,其他屬性含義可以參考介面 API 文檔。
console.log(result);
// {
// webRTC: true,
// customCapture: true,
// camera: true,
// microphone: true,
// videoCodec: { H264: true, H265: false, VP8: true, VP9: true },
// screenSharing: true,
// errInfo: {}
// }
您還可以通過 ZEGO 提供的實時音視頻推拉流線上檢測工具 線上檢測工具,在需要檢測的瀏覽器中打開,直接檢測瀏覽器的相容性。請參考 瀏覽器相容性說明 獲取 音視頻SDK 支持的瀏覽器相容版本。
4.4 登錄房間
1. 生成 Token
登錄房間需要用於驗證身份的 Token,開發者可直接在 ZEGO 控制台獲取臨時 Token(有效期為 24 小時)來使用,詳情請參考 控制台 - 項目管理 中的 “項目信息”。
臨時 Token 僅供調試,正式上線前,請從開發者的業務伺服器生成 Token,詳情可參考 使用 Token 鑒權。
2. 登錄房間
調用 loginRoom
介面,傳入房間 ID 參數 “roomID”、“token” 和用戶參數 “user”,根據實際情況傳入參數 “config”,登錄房間。
- “roomID”、“userID” 和 “userName” 參數的取值都為自定義。
- “roomID” 和 “userID” 都必須唯一,建議開發者將 “userID” 設置為一個有意義的值,可將其與自己的業務賬號系統進行關聯。
- 只有調用
loginRoom
介面登錄房間時傳入ZegoRoomConfig
配置,且 “userUpdate” 參數取值為 “true” 時,用戶才能收到roomUserUpdate
回調。
// 登錄房間,成功則返回 true
// userUpdate 設置為 true 才能收到 roomUserUpdate 回調。
let userID = Util.getBrow() + '_' + new Date().getTime();
let userName = "user0001";
let roomID = "0001";
let token = ;
// 為避免錯過任何通知,您需要在登錄房間前先監聽用戶加入/退出房間、房間連接狀態變更、推流狀態變更等回調。
zg.on('roomStateChanged', async (roomID, reason, errorCode, extendedData) => {
})
zg.loginRoom(roomID, token, { userID, userName: userID }, { userUpdate: true }).then(result => {
if (result == true) {
console.log("login success")
}
});
您可以監聽 roomStateChanged
回調實時監控自己與房間的連接狀態。只有當房間狀態是連接成功時,才能進行推流、拉流等操作。
4.5 預覽自己的畫面,並推送到 ZEGO 音視頻雲
- 創建流並預覽自己的畫面
開始推流前需要創建本端的音視頻流,調用 createStream
介面獲取媒體流對象,預設會採集攝像頭畫面和麥克風聲音。媒體流對象可以使用 createLocalStreamView
創建本地媒體流播放組件進行播放,也可以通過 video 元素 srcObject 屬性賦值進行播放。
- 需等待
createStream
介面返迴流媒體對象後,再將自己的音視頻流推送到 ZEGO 音視頻雲。
調用 startPublishingStream
介面,傳入 “streamID” 和創建流得到的流對象 “localStream”,向遠端用戶發送本端的音視頻流。
“streamID” 由您本地生成,但是需要保證同一個 AppID 下,“streamID” 全局唯一。如果同一個 AppID 下,不同用戶各推了一條 “streamID” 相同的流,會導致後推流的用戶推流失敗。
// 此處在登錄房間成功後,立即進行推流。在實現具體業務時,您可選擇其他時機進行推流,只要保證當前房間連接狀態是連接成功的即可。
zg.loginRoom(roomID, token, { userID, userName: userID }, { userUpdate: true }).then(async result => {
if (result == true) {
console.log("login success")
// 創建流、預覽
// 調用 createStream 介面後,需要等待 ZEGO 伺服器返迴流媒體對象才能執行後續操作
const localStream = await zg.createStream();
// 創建媒體流播放組件對象,用於預覽本地流
const localView = zg.createLocalStreamView(localStream);
// 將播放組件掛載到頁面,"local-video" 為組件容器 DOM 元素的 id 。
localView.play("local-video");
// 開始推流,將自己的音視頻流推送到 ZEGO 音視頻雲
let streamID = new Date().getTime().toString();
zg.startPublishingStream(streamID, localStream)
}
});
(可選)設置音視頻採集參數
通過屬性設置相關採集參數
可根據需要通過 createStream
介面中的如下屬性設置音視頻相關採集參數,詳情可參考 自定義視頻採集:
-
camera
:攝像頭麥克風采集流相關配置 -
screen
:屏幕捕捉採集流相關配置 -
custom
:第三方流採集相關配置
4.6 拉取其他用戶的音視頻
進行視頻通話時,我們需要拉取到其他用戶的音視頻。
房間內有其他用戶加入時,SDK 會觸發 roomStreamUpdate
回調,通知房間內有流新增,基於此可獲取其他用戶的 “streamID”。此時,調用 startPlayingStream
介面根據傳入的其他用戶的 “streamID”,拉取遠端已推送到 ZEGO 伺服器的音視頻畫面。若需要從 CDN 拉流,可參考 使用 CDN 直播。
// 流狀態更新回調
zg.on('roomStreamUpdate', async (roomID, updateType, streamList, extendedData) => {
// 當 updateType 為 ADD 時,代表有音視頻流新增,此時可以調用 startPlayingStream 介面拉取播放該音視頻流
if (updateType == 'ADD') {
// 流新增,開始拉流
// 這裡為了使示例代碼更加簡潔,我們只拉取新增的音視頻流列表中第的第一條流,在實際的業務中,建議開發者迴圈遍歷 streamList ,拉取每一條音視頻流
const streamID = streamList[0].streamID;
// streamList 中有對應流的 streamID
const remoteStream = await zg.startPlayingStream(streamID);
// 創建媒體流播放組件對象,用於播放遠端媒體流 。
const remoteView = zg.createRemoteStreamView(remoteStream);
// 將播放組件掛載到頁面,"remote-video" 為組件容器 DOM 元素的 id 。
remoteView.play("remote-video");
} else if (updateType == 'DELETE') {
// 流刪除,停止拉流
}
});
- 部分瀏覽器因自動播放限制策略問題,使用
ZegoStreamView
媒體流播放組件進行播放媒體流可能受阻,SDK 預設會在界面上彈窗提示恢復播放。 - 您可以將 ZegoStreamView.play() 方法的第二個參數 options.enableAutoplayDialog 設置為 false 關閉自動彈窗,通過在
autoplayFailed
事件回調中,在頁面上顯示一個按鈕,引導用戶點擊恢復播放。
至此,您已經成功實現了簡單的實時音視頻通話,可在瀏覽器中打開 "index.html",體驗實時音視頻功能。
5 實時音視頻SDK常用功能
5.1 常用通知回調
// 房間連接狀態更新回調
// 本地調用 loginRoom 加入房間時,您可通過監聽 roomStateChanged 回調實時監控自己在本房間內的連接狀態。
zg.on('roomStateChanged', (roomID, reason, errorCode, extendData) => {
if (reason == 'LOGINING') {
// 登錄中
} else if (reason == 'LOGINED') {
// 登錄成功
//只有當房間狀態是登錄成功或重連成功時,推流(startPublishingStream)、拉流(startPlayingStream)才能正常收發音視頻
//將自己的音視頻流推送到 ZEGO 音視頻雲
} else if (reason == 'LOGIN_FAILED') {
// 登錄失敗
} else if (reason == 'RECONNECTING') {
// 重連中
} else if (reason == 'RECONNECTED') {
// 重連成功
} else if (reason == 'RECONNECT_FAILED') {
// 重連失敗
} else if (reason == 'KICKOUT') {
// 被踢出房間
} else if (reason == 'LOGOUT') {
// 登出成功
} else if (reason == 'LOGOUT_FAILED') {
// 登出失敗
}
});
//房間內其他用戶推送的音視頻流新增/減少的通知
//自己推送的流不能在這裡接收到通知
zg.on('roomStreamUpdate', async (roomID, updateType, streamList, extendedData) => {
if (updateType == 'ADD') {
// 流新增
for (var i = 0; i < streamList.length; i++) {
console.log('房間',roomID,'內新增了流:', streamList[i]['streamID'])
}
const message = "其他用戶的視頻流streamID: " + streamID.toString();
} else if (updateType == 'DELETE') {
// 流刪除
for (var i = 0; i < streamList.length; i++) {
console.log('房間',roomID,'內減少了流:', streamList[i]['streamID'])
}
}
});
//房間內其他用戶進出房間的通知
//只有調用 loginRoom 登錄房間時傳入 ZegoRoomConfig,且 ZegoRoomConfig 的 userUpdate 參數為 “true” 時,用戶才能收到 roomUserUpdate回調。
zg.on('roomUserUpdate', (roomID, updateType, userList) => {
if (updateType == 'ADD') {
for (var i = 0; i < userList.length; i++) {
console.log(userList[i]['userID'], '加入了房間:', roomID)
}
} else if (updateType == 'DELETE') {
for (var i = 0; i < userList.length; i++) {
console.log(userList[i]['userID'], '退出了房間:', roomID)
}
}
});
//用戶推送音視頻流的狀態通知
//用戶推送音視頻流的狀態發生變更時,會收到該回調。如果網路中斷導致推流異常,SDK 在重試推流的同時也會通知狀態變化。
zg.on('publisherStateUpdate', result => {
// 推流狀態更新回調
var state = result['state']
var streamID = result['streamID']
var errorCode = result['errorCode']
var extendedData = result['extendedData']
if (state == 'PUBLISHING') {
console.log('成功推送音視頻流:', streamID);
} else if (state == 'NO_PUBLISH') {
console.log('未推送音視頻流');
} else if (state == 'PUBLISH_REQUESTING') {
console.log('請求推送音視頻流:', streamID);
}
console.log('錯誤碼:', errorCode,' 額外信息:', extendedData)
})
//推流質量回調。
//成功推流後,您會定時收到回調音視頻流質量數據(如解析度、幀率、碼率等)。
zg.on('publishQualityUpdate', (streamID, stats) => {
// 推流質量回調
console.log('流質量回調')
})
//用戶拉取音視頻流的狀態通知
//用戶拉取音視頻流的狀態發生變更時,會收到該回調。如果網路中斷導致拉流異常,SDK 會自動進行重試。
zg.on('playerStateUpdate', result => {
// 拉流狀態更新回調
var state = result['state']
var streamID = result['streamID']
var errorCode = result['errorCode']
var extendedData = result['extendedData']
if (state == 'PLAYING') {
console.log('成功拉取音視頻流:', streamID);
} else if (state == 'NO_PLAY') {
console.log('未拉取音視頻流');
} else if (state == 'PLAY_REQUESTING') {
console.log('請求拉取音視頻流:', streamID);
}
console.log('錯誤碼:', errorCode,' 額外信息:', extendedData)
})
//拉取音視頻流時的質量回調。
//成功拉流後,您會定時收到拉取音視頻流時的質量數據通知(如解析度、幀率、碼率等)。
zg.on('playQualityUpdate', (streamID,stats) => {
// 拉流質量回調
})
//收到廣播消息的通知
zg.on('IMRecvBroadcastMessage', (roomID, chatData) => {
console.log('廣播消息IMRecvBroadcastMessage', roomID, chatData[0].message);
alert(chatData[0].message)
});
//收到彈幕消息的通知
zg.on('IMRecvBarrageMessage', (roomID, chatData) => {
console.log('彈幕消息IMRecvBroadcastMessage', roomID, chatData[0].message);
alert(chatData[0].message)
});
//收到自定義信令消息的通知
zg.on('IMRecvCustomCommand', (roomID, fromUser, command) => {
console.log('自定義消息IMRecvCustomCommand', roomID, fromUser, command);
alert(command)
});
5.2 停止音視頻通話
1. 停止推流、銷毀流
調用 stopPublishingStream
介面停止向遠端用戶發送本端的音視頻流。調用 destroyStream
介面銷毀創建的流數據,銷毀流後開發需自行銷毀 video(停止採集)。
// 根據本端 streamID 停止推流
zg.stopPublishingStream(streamID)
// localStream 是調用 createStream 介面獲取的 MediaStream 對象
zg.destroyStream(localStream)
2. 停止拉流
調用 stopPlayingStream
介面停止拉取遠端推送的音視頻流。
// 流狀態更新回調
zg.on('roomStreamUpdate', async (roomID, updateType, streamList, extendedData) => {
if (updateType == 'ADD') {
// 流新增,開始拉流
} else if (updateType == 'DELETE') {
// 流刪除,通過流刪除列表 streamList 中每個流的 streamID 進行停止拉流。
const streamID = streamList[0].streamID;
zg.stopPlayingStream(streamID)
}
});
3. 退出房間
調用 logoutRoom
介面退出房間。
zg.logoutRoom(roomID)
以上整個推拉流過程的 API 調用時序可參考下圖:
6 調試視頻通話功能
在真機中運行項目,運行成功後,可以看到本端視頻畫面。
為方便體驗,ZEGO 提供了一個 Web 端調試示例 ,在該頁面下,輸入相同的 AppID、RoomID,輸入一個不同的 UserID,即可加入同一房間與真機設備互通。當成功開始音視頻通話時,可以聽到遠端的音頻,看到遠端的視頻畫面。
7 獲取音視頻SDK更多支持
獲取本文的Demo、開發文檔、技術支持,訪問即構文檔中心
近期有開發規劃的開發者可上即構官網查看,恰逢即構七周年全線音視頻產品1折的優惠,聯繫商務獲取RTC產品優惠;
音視頻場景解決方案分享,更多詳情可搜索官網(https://zegoguanwang.datasink.sensorsdata.cn/t/pB)