基於 Web SDK 實現視訊通話場景 | 聲網 SDK 教程
聲網視訊 SDK 被廣泛應用於多種實時互動場景中,例如視訊會議、視訊通話、音視訊社交、線上教育等。為了讓剛剛接觸聲網 SDK 的開發者,可以更順暢地實現基礎的視訊通話功能,我們基於聲網 Web SDK 4.x 版本梳理了本篇教程。
在本文末,會提供相應 Demo 、文件地址供大家參考使用。同時,歡迎點選此處註冊聲網賬號體驗。聲網每個月會為開發者提供 10000 分鐘的免費額度。
本文為「 聲網 SDK 教程 」系列內容
01 Demo 體驗
我們在 GitHub 上提供一個開源的基礎視訊通話示例專案,在開始開發之前你可以通過該示例專案體驗音視訊通話效果。Demo 與線上體驗地址,可在文末獲取。
02 動手實踐
實踐任務
從 Web 前端頁面引入聲網 SDK,發起視訊通話。
開發環境
聲網 SDK 的相容性良好,對硬體裝置和軟體系統的要求不高,開發環境和測試環境滿足以下條件即可:
- Chrome
- Firefox
- Safari
- Edge
以下是本文的開發環境和測試環境:
開發環境
- MacBook Pro (13-inch, M1, 2020)
- Visual Studio Code (1.67.1)
測試環境
- Chrome (101.0.4951.64)
如果你此前還未接觸過聲網 SDK,那麼你還需要做以下準備工作:
- 註冊一個聲網賬號,進入後臺建立 AppID、獲取 Token;
- 下載聲網官方最新的 視訊 SDK。
專案設定
檔案組織結構
實現視訊通話之前,參考如下步驟設定你的專案:
如需建立新專案,可以在 Visual Studio Code 裡 File > New Window,建立 Web 專案。完整的目錄結構如下,根據個人經驗會有所變化。
. ├── index.css # 用於設計 Web 應用的使用者介面樣式 ├── index.html # 用於設計 Web 應用的使用者介面 ├── index.js # 通過 AgoraRTCClient 實現具體應用邏輯的程式碼。 └── vendor # 第三方前端外掛,輔助頁面佈局和互動,本教程中是下載到本地使用,你也可以使用 CDN 的方式 ├── bootstrap.bundle.min.js ├── bootstrap.min.css └── jquery-3.4.1.min.js
整合聲網 SDK
可以下載到本地使用,也可以直接使用聲網的 CDN 引入, 本文推薦使用 CDN 方式整合聲網 SDK。
在 index.html 中新增以下程式碼
<!DOCTYPE html> ... <link rel="stylesheet" href="./vendor/bootstrap.min.css"> <link rel="stylesheet" href="./index.css"> ... <script src="./vendor/jquery-3.4.1.min.js"></script> <script src="./vendor/bootstrap.bundle.min.js"></script> <script src="http://download.agora.io/sdk/release/AgoraRTC_N.js"></script> <script src="./index.js"></script> ...
最終完整程式碼為
可以直接複製執行。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Basic Video Call -- Agora</title> <link rel="stylesheet" href="./vendor/bootstrap.min.css"> <link rel="stylesheet" href="./index.css"> </head> <body> <div class="container-fluid banner"> <p class="banner-text">Basic Video Call</p> <a style="color: rgb(255, 255, 255);fill: rgb(255, 255, 255);fill-rule: evenodd; position: absolute; right: 10px; top: 4px;" class="Header-link " href="http://github.com/AgoraIO-Community/AgoraWebSDK-NG/tree/master/Demo"> <svg class="octicon octicon-mark-github v-align-middle" height="32" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg> </a> </div> <div id="success-alert" class="alert alert-success alert-dismissible fade show" role="alert"> <strong>Congratulations!</strong><span> You can invite others join this channel by click </span><a href="" target="_blank">here</a> <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div id="success-alert-with-token" class="alert alert-success alert-dismissible fade show" role="alert"> <strong>Congratulations!</strong><span> Joined room successfully. </span> <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div id="success-alert-with-token" class="alert alert-success alert-dismissible fade show" role="alert"> <strong>Congratulations!</strong><span> Joined room successfully. </span> <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="container"> <form id="join-form"> <div class="row join-info-group"> <div class="col-sm"> <p class="join-info-text">AppID</p> <input id="appid" type="text" placeholder="enter appid" required> <p class="tips">If you don`t know what is your appid, checkout <a href="http://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#a-nameappidaapp-id">this</a></p> </div> <div class="col-sm"> <p class="join-info-text">Token(optional)</p> <input id="token" type="text" placeholder="enter token"> <p class="tips">If you don`t know what is your token, checkout <a href="http://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#a-namekeyadynamic-key">this</a></p> </div> <div class="col-sm"> <p class="join-info-text">Channel</p> <input id="channel" type="text" placeholder="enter channel name" required> <p class="tips">If you don`t know what is your channel, checkout <a href="http://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#channel">this</a></p> </div> </div> <div class="button-group"> <button id="join" type="submit" class="btn btn-primary btn-sm">Join</button> <button id="leave" type="button" class="btn btn-primary btn-sm" disabled>Leave</button> </div> </form> <div class="row video-group"> <div class="col"> <p id="local-player-name" class="player-name"></p> <div id="local-player" class="player"></div> </div> <div class="w-100"></div> <div class="col"> <div id="remote-playerlist"></div> </div> </div> </div> <script src="./vendor/jquery-3.4.1.min.js"></script> <script src="./vendor/bootstrap.bundle.min.js"></script> <script src="http://download.agora.io/sdk/release/AgoraRTC_N.js"></script> <script src="./index.js"></script> </body> </html>
視訊通話邏輯
實現視訊通話邏輯
下圖展示視訊通話的 API 呼叫時序,注意圖中的方法是對不同的物件呼叫的。
參考以下步驟實現音視訊通話的邏輯:
1.呼叫 createClient 方法建立 AgoraRTCClient
物件。
2.呼叫 join 方法加入一個 RTC 頻道,你需要在該方法中傳入 App ID 、使用者 ID、Token、頻道名稱。
3.先呼叫 createMicrophoneAudioTrack 通過麥克風採集的音訊建立本地音訊軌道物件,呼叫 createCameraVideoTrack 通過攝像頭採集的視訊建立本地視訊軌道物件;然後呼叫 publish 方法,將這些本地音視訊軌道物件當作引數即可將音視訊釋出到頻道中。
4.當一個遠端使用者加入頻道併發布音視訊軌道時:
a.監聽 client.on("user-published") 事件。當 SDK 觸發該事件時,在這個事件回撥函式的引數中你可以獲取遠端使用者 AgoraRTCRemoteUser
物件 。
b.呼叫 subscribe 方法訂閱遠端使用者 AgoraRTCRemoteUser
物件,獲取遠端使用者的遠端音訊軌道 RemoteAudioTrack
和遠端視訊軌道 RemoteVideoTrack
物件。
c.呼叫 play
方法播放遠端音視訊軌道。
注:以下程式碼都將在 index.js 中新增**
初始化client
var client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
加入RTC 頻道並建立本地音訊軌道
// Join a channel and create local tracks. Best practice is to use Promise.all and run them concurrently. [ options.uid, localTracks.audioTrack, localTracks.videoTrack ] = await Promise.all([ // Join the channel. client.join(options.appid, options.channel, options.token || null, options.uid || null), // Create tracks to the local microphone and camera. AgoraRTC.createMicrophoneAudioTrack(), AgoraRTC.createCameraVideoTrack() ]);
播放本地視訊
// Play the local video track to the local browser and update the UI with the user ID. localTracks.videoTrack.play("local-player");
釋出本地音視訊到頻道中
// Publish the local video and audio tracks to the channel. await client.publish(Object.values(localTracks));
監聽遠端使用者音視訊
// Add an event listener to play remote tracks when remote user publishes. client.on("user-published", handleUserPublished); client.on("user-unpublished", handleUserUnpublished); function handleUserPublished(user, mediaType) { const id = user.uid; remoteUsers[id] = user; subscribe(user, mediaType); } function handleUserUnpublished(user, mediaType) { if (mediaType === 'video') { const id = user.uid; delete remoteUsers[id]; $(`#player-wrapper-${id}`).remove(); } } async function subscribe(user, mediaType) { const uid = user.uid; // subscribe to a remote user await client.subscribe(user, mediaType); console.log("subscribe success"); if (mediaType === 'video') { const player = $(` <div id="player-wrapper-${uid}"> <p class="player-name">remoteUser(${uid})</p> <div id="player-${uid}" class="player"></div> </div> `); $("#remote-playerlist").append(player); user.videoTrack.play(`player-${uid}`); } if (mediaType === 'audio') { user.audioTrack.play(); } }
離開頻道
async function leave() { for (trackName in localTracks) { var track = localTracks[trackName]; if(track) { track.stop(); track.close(); localTracks[trackName] = undefined; } } // Remove remote users and player views. remoteUsers = {}; $("#remote-playerlist").html(""); // leave the channel await client.leave(); }
最終完整的程式碼
// create Agora client var client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" }); var localTracks = { videoTrack: null, audioTrack: null }; var remoteUsers = {}; // Agora client options var options = { appid: null, channel: null, uid: null, token: null }; // the demo can auto join channel with params in url $(() => { var urlParams = new URL(location.href).searchParams; options.appid = urlParams.get("appid"); options.channel = urlParams.get("channel"); options.token = urlParams.get("token"); if (options.appid && options.channel) { $("#appid").val(options.appid); $("#token").val(options.token); $("#channel").val(options.channel); $("#join-form").submit(); } }) $("#join-form").submit(async function (e) { e.preventDefault(); $("#join").attr("disabled", true); try { options.appid = $("#appid").val(); options.token = $("#token").val(); options.channel = $("#channel").val(); await join(); if(options.token) { $("#success-alert-with-token").css("display", "block"); } else { $("#success-alert a").attr("href", `index.html?appid=${options.appid}&channel=${options.channel}&token=${options.token}`); $("#success-alert").css("display", "block"); } } catch (error) { console.error(error); } finally { $("#leave").attr("disabled", false); } }) $("#leave").click(function (e) { leave(); }) async function join() { // add event listener to play remote tracks when remote user publishs. client.on("user-published", handleUserPublished); client.on("user-unpublished", handleUserUnpublished); // join a channel and create local tracks, we can use Promise.all to run them concurrently [ options.uid, localTracks.audioTrack, localTracks.videoTrack ] = await Promise.all([ // join the channel client.join(options.appid, options.channel, options.token || null), // create local tracks, using microphone and camera AgoraRTC.createMicrophoneAudioTrack(), AgoraRTC.createCameraVideoTrack() ]); // play local video track localTracks.videoTrack.play("local-player"); $("#local-player-name").text(`localVideo(${options.uid})`); // publish local tracks to channel await client.publish(Object.values(localTracks)); console.log("publish success"); } async function leave() { for (trackName in localTracks) { var track = localTracks[trackName]; if(track) { track.stop(); track.close(); localTracks[trackName] = undefined; } } // remove remote users and player views remoteUsers = {}; $("#remote-playerlist").html(""); // leave the channel await client.leave(); $("#local-player-name").text(""); $("#join").attr("disabled", false); $("#leave").attr("disabled", true); console.log("client leaves channel success"); } async function subscribe(user, mediaType) { const uid = user.uid; // subscribe to a remote user await client.subscribe(user, mediaType); console.log("subscribe success"); if (mediaType === 'video') { const player = $(` <div id="player-wrapper-${uid}"> <p class="player-name">remoteUser(${uid})</p> <div id="player-${uid}" class="player"></div> </div> `); $("#remote-playerlist").append(player); user.videoTrack.play(`player-${uid}`); } if (mediaType === 'audio') { user.audioTrack.play(); } } function handleUserPublished(user, mediaType) { const id = user.uid; remoteUsers[id] = user; subscribe(user, mediaType); } function handleUserUnpublished(user) { const id = user.uid; delete remoteUsers[id]; $(`#player-wrapper-${id}`).remove(); }
執行效果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-qHpzqvtR-1658572023354)(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)]在瀏覽器開兩個tab執行網頁,使用兩個使用者加入同一個頻道,如果能看見兩個自己,說明你成功了。
03 完整程式碼下載
訪問聲網文件中心,根據下圖所示路徑,下載對應 SDK 壓縮包。壓縮包中包含完整程式碼。

參考連結
1.Github 原始碼
http://github.com/AgoraIO/API-Examples-Web/tree/main/Demo/basicVideoCall
2.線上體驗Demo
http://webdemo.agora.io/basicVideoCall/index.html
3.聲網文件中心
- 天翼雲全場景業務無縫替換至國產原生作業系統CTyunOS!
- 以羊了個羊為例,淺談小程式抓包與響應報文修改
- 這幾種常見的 JVM 調優場景,你知道嗎?
- 如此狂妄,自稱高效能佇列的Disruptor有啥來頭?
- 為什麼要學習GoF設計模式?
- 827. 最大人工島 : 簡單「並查集 列舉」運用題
- 手把手教你如何使用 Timestream 實現物聯網時序資料儲存和分析
- 850. 矩形面積 II : 掃描線模板題
- Java 併發程式設計解析 | 基於JDK原始碼解析Java領域中的併發鎖,我們可以從中學習到什麼內容?
- 【手把手】光說不練假把式,這篇全鏈路壓測實踐探索
- 大廠鍾愛的全鏈路壓測有什麼意義?四種壓測方案詳細對比分析
- 寫個續集,填坑來了!關於“Thread.sleep(0)這一行‘看似無用’的程式碼”裡面留下的坑。
- 857. 僱傭 K 名工人的最低成本 : 列舉 優先佇列(堆)運用題
- Vue3 實現一個自定義toast(小彈窗)
- 669. 修剪二叉搜尋樹 : 常規樹的遍歷與二叉樹性質
- 讀完 RocketMQ 原始碼,我學會了如何優雅的建立執行緒
- 效能調優——小小的log大大的坑
- 1582. 二進位制矩陣中的特殊位置 : 簡單模擬題
- elementui原始碼學習之仿寫一個el-switch
- 646. 最長數對鏈 : 常規貪心 DP 運用題