WebRTC實現簡單音視頻通話功能

来源:https://www.cnblogs.com/zegodeveloper/archive/2022/07/27/16523732.html
-Advertisement-
Play Games

1 WebRTC音視頻通話功能簡介 本文介紹如何基於WebRTC快速實現一個簡單的實時音視頻通話。 在開始之前,您可以先瞭解一些實時音視頻推拉流相關的基礎概念: 流:一組按指定編碼格式封裝的音視頻數據內容。一個流可以包含幾個軌道,比如視頻和音頻軌道。 推流:把採集階段封包好的音視頻數據流推送到 ZE ...


1 WebRTC音視頻通話功能簡介

本文介紹如何基於WebRTC快速實現一個簡單的實時音視頻通話。

在開始之前,您可以先瞭解一些實時音視頻推拉流相關的基礎概念:

  • 流:一組按指定編碼格式封裝的音視頻數據內容。一個流可以包含幾個軌道,比如視頻和音頻軌道。
  • 推流:把採集階段封包好的音視頻數據流推送到 ZEGO 實時音視頻雲的過程。
  • 拉流:從 ZEGO 實時音視頻雲將已有音視頻數據流拉取播放的過程。
  • 房間:是 ZEGO 提供的音視頻空間服務,用於組織用戶群,同一房間內的用戶可以互相收發實時音視頻及消息。
    1. 用戶需要先登錄某個房間,才能進行音視頻推流、拉流操作。
    2. 用戶只能收到自己所在房間內的相關消息(用戶進出、音視頻流變化等)。

更多相關概念可參考即構官網關於音視頻SDK的介紹 術語說明

2 實現WebRTC視頻通話的前提條件

在實現基本的WebRTC實時音視頻功能之前,請確保:

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實時音視頻通話主要流程如下:

  1. 用戶 A 創建實例,登錄房間。(登錄成功後,可預覽自己的畫面並推流。)
  2. 用戶 B 創建實例,登錄同一個房間。登錄成功後,用戶 B 開始推流,此時 SDK 會觸發 roomStreamUpdate 回調,表示房間內有流的變化。
  3. 用戶 A 可通過監聽 roomStreamUpdate 回調,當回調通知有流新增時,獲取用戶 B 的流 ID,來拉取播放用戶 B 剛剛推送的流。

image.png

4.1 創建WebRTC實時音視頻通話界面

為方便實現基本的WebRTC實時音視頻功能,您可參考WebRTC實時音視頻的示例代碼和下圖實現一個簡單實時音視頻功能的頁面。

image.png

打開或新建 “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 創建引擎並監聽回調

  1. 創建並初始化一個 ZegoExpressEngine 的實例,將您項目的 AppID 傳入參數 “appID”,Server 傳入參數 “server”。

  2. 即構實時音視頻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 音視頻雲

  1. 創建流並預覽自己的畫面

開始推流前需要創建本端的音視頻流,調用 createStream 介面獲取媒體流對象,預設會採集攝像頭畫面和麥克風聲音。媒體流對象可以使用 createLocalStreamView 創建本地媒體流播放組件進行播放,也可以通過 video 元素 srcObject 屬性賦值進行播放。

  1. 需等待 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 調用時序可參考下圖:
image.png

6 調試視頻通話功能

在真機中運行項目,運行成功後,可以看到本端視頻畫面。

為方便體驗,ZEGO 提供了一個 Web 端調試示例 ,在該頁面下,輸入相同的 AppID、RoomID,輸入一個不同的 UserID,即可加入同一房間與真機設備互通。當成功開始音視頻通話時,可以聽到遠端的音頻,看到遠端的視頻畫面。

7 獲取音視頻SDK更多支持

獲取本文的Demo、開發文檔、技術支持,訪問即構文檔中心

近期有開發規劃的開發者可上即構官網查看,恰逢即構七周年全線音視頻產品1折的優惠,聯繫商務獲取RTC產品優惠;

音視頻場景解決方案分享,更多詳情可搜索官網(https://zegoguanwang.datasink.sensorsdata.cn/t/pB)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言: ​ 從64位開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小對象的存儲。 Tagged Pointer主要為瞭解決兩個問題: 記憶體資源浪費,堆區需要額外的開闢空間 訪問效率,每次set/get都需要訪問堆區,浪費時間, 而且需要 ...
  • HMS Core音頻編輯服務(Audio Editor Kit)6.6.0版本上線,新增歌聲合成能力。通過歌詞和曲調,結合不同的曲風讓機器也能生成真實度極高的歌聲。支持字級別輸入歌詞進行音素轉換,生成對應歌詞的歌聲,可靈活調整音高、滑音、呼吸音、顫音等細節參數,讓歌聲更真實。 歌聲合成服務可廣泛應用 ...
  • HTML 一、認識HTML 什麼是HTML? HTML 是用來描述網頁的一種語言 HTML 指的是超文本標記語言: HyperText Markup Language HTML 不是一種編程語言,而是一種標記語言 標記語言是一套標記標簽 (markup tag) HTML 使用標記標簽來描述網頁 H ...
  • 本文摘要:主要通過實操講解運用Webpack 5 CSS常用配置的方法步驟 前文已談到可以通過配置 css-loader 和 style-loader,使 webpack5 具有處理 CSS 資源的能力。css-loader 首先會分析出各個 CSS文件之間的關係,把各個CSS文件合併為一大段 CS ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 技術棧是 Vue 的同學,在面試中難免會被問到 Vue2 和 Vue3 的相關知識點的實現原理和比較,面試官是步步緊逼,一環扣一環。 Vue2 的響應式原理是怎麼樣的? Vue3 的響應式原理又是怎麼樣的? Vue2 中是怎麼監測數 ...
  • 本文將介紹用於佈局的容器組件,使用 `Flexbox` 功能將其所控制區域設定為特定的佈局,方便快速搭建頁面的基本結構。 ...
  • 1、 Vue概述 Vue (讀音/vju/, 類似於view)是一套用於構建用戶界面的漸進式框架,發佈於2014年2月。 與其它大型框架不同的是,Vue被設計為可以自底向上逐層應用。 Vue的核心庫只關註視圖層,不僅易於上手,還便於與第三方庫(如: vue-router: 跳轉,vue-resour ...
  • vue項目導航菜單問題 目標:橫向菜單點擊跳轉,顏色變換,刷新可保持狀態 // 模板template中通過迴圈菜單列表生成,動態類名改變顏色 <li v-for="(item, index) in navList" :key="index" v-text="item.name" :class="{ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...