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。
推薦閱讀:
- 在Asp.net core項目中使用WebSocket
- goland 實現websocket server的示例代碼
- vue實現websocket客服聊天功能
- IOS之WebSocket框架Starscream案例詳解
- springboot簡單接入websocket的操作方法