C# WebApi+Webrtc局域網音視頻通話實例

C# WebApi+Webrtc 局域網音視頻通話示例,供大傢參考,具體內容如下

本示例通過IIS部署webapi,利用websocket進行webrtc消息交換,通過Chrome瀏覽器訪問,可實現局域網內webrtc 音視頻通話。

通過Chrome瀏覽器打開localhost/live.html本地網址,打開兩個本地網,點擊任意頁面連接按鈕即聯通。

本示例未實現NAT穿透處理,互聯網無法聯通,如需NAT穿透請自行查閱相關資料。

關於webrtc、webapi相關技術說明請自行查閱相關資料,本文不做贅述說明。

運行效果如下圖:

webapi端Handler1.ashx代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.WebSockets;

namespace webrtclan
{
    /// <summary>
    /// 離線消息
    /// </summary>
    public class MessageInfo
    {
        public MessageInfo(DateTime _MsgTime, ArraySegment<byte> _MsgContent)
        {
            MsgTime = _MsgTime;
            MsgContent = _MsgContent;
        }
        public DateTime MsgTime { get; set; }
        public ArraySegment<byte> MsgContent { get; set; }
        
    }
 

    /// <summary>
    /// Handler1 的摘要說明
    /// </summary>
    public class Handler1 : IHttpHandler
    {
        private static Dictionary<string, WebSocket> CONNECT_POOL = new Dictionary<string, WebSocket>();//用戶連接池
        private static Dictionary<string, List<MessageInfo>> MESSAGE_POOL = new Dictionary<string, List<MessageInfo>>();//離線消息池
       

        public void ProcessRequest(HttpContext context)
        {
            
            if (context.IsWebSocketRequest)
            {
                context.Response.ContentType = "application/json";
                context.Response.Charset = "utf-8";
                context.AcceptWebSocketRequest(ProcessMsg);
            }
        }
       

        private async Task ProcessMsg(AspNetWebSocketContext context)
        {
            WebSocket socket = context.WebSocket;
            string user = context.QueryString["user"].ToString();
            
            try
            {
                #region 用戶添加連接池
                //第一次open時,添加到連接池中
                if (!CONNECT_POOL.ContainsKey(user))
                {
                    CONNECT_POOL.Add(user, socket);//不存在,添加
                }
                else
                {
                    if (socket != CONNECT_POOL[user])//當前對象不一致,更新
                    {
                        CONNECT_POOL[user] = socket;
                    }
                }
                #endregion

                //#region 連線成功
                //for (int cp = 0; cp < CONNECT_POOL.Count; cp++)
                //{
                //    if (CONNECT_POOL.ElementAt(cp).Key != user)
                //    {
                //        string joinedmsg = "{\"FROM\":\"" + user + "\",\"event\":\"joined\"}";
                //        ArraySegment<byte> joinedmsgbuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(joinedmsg));
                //        WebSocket destSocket = CONNECT_POOL.ElementAt(cp).Value;//目的客戶端
                //        await destSocket.SendAsync(joinedmsgbuffer, WebSocketMessageType.Text, true, CancellationToken.None);
                //    }
                //}
                //#endregion

                #region 離線消息處理
                if (MESSAGE_POOL.ContainsKey(user))
                {
                    List<MessageInfo> msgs = MESSAGE_POOL[user];
                    foreach (MessageInfo item in msgs)
                    {
                        await socket.SendAsync(item.MsgContent, WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    MESSAGE_POOL.Remove(user);//移除離線消息
                }
                #endregion
                
                while (true)
                {
                    if (socket.State == WebSocketState.Open)
                    {
                        ArraySegment<byte> wholemessage= new ArraySegment<byte>(new byte[10240]);

                        
                        int i = 0;

                       
                        WebSocketReceiveResult dresult;
                        do
                        { 
                            //因為websocket每一次發送的數據會被tcp分包
                            //所以必須判斷接收到的消息是否完整
                            //不完整就要繼續接收並拼接數據包
                            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
                            dresult = await socket.ReceiveAsync(buffer, CancellationToken.None);
                            string message1 = Encoding.UTF8.GetString(buffer.Array);                            
                            buffer.Array.CopyTo(wholemessage.Array,i);
                            i += 2048;
                        } while (false == dresult.EndOfMessage);                       

                        //string message = Encoding.UTF8.GetString(wholemessage.Array);
                        //message = message.Replace("\0", "").Trim();
                        //JavaScriptSerializer serializer = new JavaScriptSerializer();
                        //Dictionary<string, object> json = (Dictionary<string, object>)serializer.DeserializeObject(message);
                        //string target = (string)json.ElementAt(1).Value;

                        #region 消息處理(字符截取、消息轉發)
                        try
                        {
                            #region 關閉Socket處理,刪除連接池
                            if (socket.State != WebSocketState.Open)//連接關閉
                            {
                                if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//刪除連接池
                                break;
                            }
                            #endregion
                            
                            for (int cp = 0; cp < CONNECT_POOL.Count; cp++)
                            {
                                 //if (CONNECT_POOL.ElementAt(cp).Key!=target)
                                 //   {
                                        WebSocket destSocket = CONNECT_POOL.ElementAt(cp).Value;//目的客戶端
                                        await destSocket.SendAsync(wholemessage, WebSocketMessageType.Text, true, CancellationToken.None);
                                 //  }
                             }
                           

                            //if (CONNECT_POOL.ContainsKey(descUser))//判斷客戶端是否在線
                            //{
                            //    WebSocket destSocket = CONNECT_POOL[descUser];//目的客戶端
                            //    if (destSocket != null && destSocket.State == WebSocketState.Open)
                            //        await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
                            //}
                            //else
                            //{
                            //    _ = Task.Run(() =>
                            //      {
                            //          if (!MESSAGE_POOL.ContainsKey(descUser))//將用戶添加至離線消息池中
                            //            MESSAGE_POOL.Add(descUser, new List<MessageInfo>());
                            //          MESSAGE_POOL[descUser].Add(new MessageInfo(DateTime.Now, buffer));//添加離線消息
                            //    });
                            //}
                        }
                        catch (Exception exs)
                        {
                            //消息轉發異常處理,本次消息忽略 繼續監聽接下來的消息
                        }
                        #endregion
                    }
                    else
                    {
                        if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//刪除連接池                        
                        break;
                    }
                }//while end
            }
            catch (Exception ex)
            {
                //整體異常處理
                if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);
                
            }
        }

    

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }


    }
}

live.html客戶端代碼如下:

<!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>webrtc</title>
    <style>
        #yours {
            width: 200px;
            position: absolute;
            top: 50px;
            left: 100px;
        }
        #theirs {
            width: 600px;
            position: absolute;
            top: 50px;
            left: 400px;
        }
    </style>
</head>
<body>
    <button onclick="createOffer()">建立連接</button>
    <video id="yours" autoplay controls="controls" ></video>
    <video id="theirs" autoplay controls="controls"></video>

</body>

<script src="webrtc.js"></script>

</html>

webrtc.js腳本代碼如下:

var websocket;

function randomNum(minNum, maxNum) {
    switch (arguments.length) {
        case 1:
            return parseInt(Math.random() * minNum + 1, 10);
            break;
        case 2:
            return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
            break;
        default:
            return 0;
            break;
    }
}
const userid = 'user' + randomNum(0, 100000);

function hasUserMedia() {
    navigator.getUserMedia = navigator.getUserMedia || navigator.msGetUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
    return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() {
    window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;
    return !!window.RTCPeerConnection;
}

var yourVideo = document.getElementById("yours");
var theirVideo = document.getElementById("theirs");
var Connection;


function startPeerConnection() {
    //return;
    var config = {
        'iceServers': [
            //{ 'urls': 'stun:stun.xten.com:3478' },
            //{ 'urls': 'stun:stun.voxgratia.org:3478' },

            //{ 'url': 'stun:stun.l.google.com:19302' }
        ]
    };
    config = {
        iceServers: [
            //{ urls: 'stun:stun.l.google.com:19302' },
            //{ urls: 'stun:global.stun.twilio.com:3478?transport=udp' }
        ]
        //sdpSemantics: 'unified-plan'
    };
    // {
    //     "iceServers": [{
    //         "url": "stun:stun.1.google.com:19302"
    //     }]
    // };
    Connection = new RTCPeerConnection(config);
    Connection.onicecandidate = function (e) {
        console.log('onicecandidate');
        if (e.candidate) {
            websocket.send(JSON.stringify({
                "userid": userid,
                "event": "_ice_candidate",
                "data": {
                    "candidate": e.candidate
                }
            }));
        }
    };
    Connection.onaddstream = function (e) {
        console.log('onaddstream');
        //theirVideo.src = window.URL.createObjectURL(e.stream);
        theirVideo.srcObject = e.stream;
    };
    Connection.onclose = function (e) {
        console.log('RTCPeerConnection close'+e);
    };
}

createSocket();
startPeerConnection();

if (hasUserMedia()) {
    navigator.getUserMedia({ video: true, audio: true },
        stream => {
            yourVideo.srcObject = stream;
            window.stream = stream;
            yourVideo.muted = true;
            Connection.addStream(stream)
        },
        err => {
            console.log(err);
        })
}


function createOffer() {
    //發送offer和answer的函數,發送本地session描述
    Connection.createOffer().then(offer => {
        Connection.setLocalDescription(offer);
        websocket.send(JSON.stringify({            
            "userid": userid,
            "event": "offer",
            "data": {
                "sdp": offer
            }
        }));
    });
}


function createSocket() {
    //websocket = null;
    websocket = new WebSocket('ws://localhost:80/Handler1.ashx?user='+userid);//('wss://www.ecoblog.online/wss');
    eventBind();
};

function eventBind() {
    //連接成功
    websocket.onopen = function (e) {
        console.log('open:' + e);
    };
    //server端請求關閉
    websocket.onclose = function (e) {
        console.log('close:' + e);
    };
    //error
    websocket.onerror = function (e) {
        console.log('error:' + e.data);
    };
    //收到消息
    websocket.onmessage = (event) => {
        if (event.data == "new user") {
            location.reload();
        } else {
            var js = event.data.replace(/[\u0000-\u0019]+/g, ""); 
            var json = JSON.parse(js);
            
            if (json.userid != userid) {
                //如果是一個ICE的候選,則將其加入到PeerConnection中,否則設定對方的session描述為傳遞過來的描述
                if (json.event === "_ice_candidate" && json.data.candidate) {
                    Connection.addIceCandidate(new RTCIceCandidate(json.data.candidate));
                }
                else if (json.event === 'offer') {
                    Connection.setRemoteDescription(json.data.sdp);
                    Connection.createAnswer().then(answer => {
                        Connection.setLocalDescription(answer);
                        //console.log(window.stream)
                        websocket.send(JSON.stringify({                            
                            "userid": userid,
                            "event": "answer",
                            "data": {
                                "sdp": answer
                            }
                        }));
                    })
                }
                else if (json.event === 'answer') {
                    Connection.setRemoteDescription(json.data.sdp);
                    //console.log(window.stream)

                }
            }
        }
    };
}

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: