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
  • Github / Gitee QQ群(1群) : 813100564 / QQ群(2群) : 579033769 視頻教學 介紹 MiniWord .NET Word模板引擎,藉由Word模板和數據簡單、快速生成文件。 Getting Started 安裝 nuget link : https:// ...
  • Array.Sort Array類中相當實用的我認為是Sort方法,相比起冗長的冒泡排序,它的出現讓排序更加的簡化 結果如下: 還可以聲明一個靜態方法用來專門調用指定數組排序,從名為 array 的一維數組中 a 索引處開始,到 b 元素 從小到大排序。 註意: a + b 不能大於 array 的 ...
  • 前言 在上一篇文章CLR類型系統概述里提到,當運行時掛起時, 垃圾回收會執行堆棧遍歷器(stack walker)去拿到堆棧上值類型的大小和堆棧根。這裡我們來翻譯BotR里一篇專門介紹Stackwalking的文章,希望能加深理解。 順便說一句,StackWalker在中文里似乎還沒有統一的翻譯,J ...
  • 使用過 nginx 的小伙伴應該都知道,這個中間件是可以設置跨域的,作為今天的主角,同樣的 反向代理中間件的 YARP 毫無意外也支持了跨域請求設置。 有些小伙伴可能會問了,怎樣才算是跨域呢? 在 HTML 中,一些標簽,例如 img、a 等,還有我們非常熟悉的 Ajax,都是可以指向非本站的資源的 ...
  • 什麼是Git Git 是一個開源的分散式版本控制系統,用於敏捷高效地處理任何或小或大的項目。 Git 是 Linus Torvalds 為了幫助管理 Linux 內核開發而開發的一個開放源碼的版本控制軟體。 Git 與常用的版本控制工具 CVS, Subversion 等不同,它採用了分散式版本庫的 ...
  • 首先CR3是什麼,CR3是一個寄存器,該寄存器內保存有頁目錄表物理地址(PDBR地址),其實CR3內部存放的就是頁目錄表的記憶體基地址,運用CR3切換可實現對特定進程記憶體地址的強制讀寫操作,此類讀寫屬於有痕讀寫,多數驅動保護都會將這個地址改為無效,此時CR3讀寫就失效了,當然如果能找到CR3的正確地址... ...
  • 說明 onlyoffice為一款開源的office線上編輯組件,提供word/excel/ppt編輯保存操作 以下操作均基於centos8系統,officeonly鏡像版本7.1.2.23 鏡像下載地址:https://yunpan.360.cn/surl_y87CKKcPdY4 (提取碼:1f92 ...
  • 二叉樹查找指定的節點 前序查找的思路 1.先判斷當前節點的no是否等於要查找的 2.如果是相等,則返回當前節點 3.如果不等,則判斷當前節點的左子節點是否為空,如果不為空,則遞歸前序查找 4.如果左遞歸前序查找,找到節點,則返回,否繼續判斷,當前的節點的右子節點是否為空,如果不為空,則繼續向右遞歸前 ...
  • ##Invalid bound statement (not found)出現原因和解決方法 ###前言: 想必各位小伙伴在碼路上經常會碰到奇奇怪怪的事情,比如出現Invalid bound statement (not found),那今天我就來分析以下出現此問題的原因。 其實出現這個問題實質就是 ...
  • ###一、背景知識 爬蟲的本質就是一個socket客戶端與服務端的通信過程,如果我們有多個url待爬取,只用一個線程且採用串列的方式執行,那隻能等待爬取一個結束後才能繼續下一個,效率會非常低。 需要強調的是:對於單線程下串列N個任務,並不完全等同於低效,如果這N個任務都是純計算的任務,那麼該線程對c ...