时间:2021-12-25 10:16:45 | 栏目:.NET代码 | 点击:次
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) } } } }; }