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 Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...