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 { ////// 离线消息 /// public class MessageInfo { public MessageInfo(DateTime _MsgTime, ArraySegment_MsgContent) { MsgTime = _MsgTime; MsgContent = _MsgContent; } public DateTime MsgTime { get; set; } public ArraySegment MsgContent { get; set; } } /// /// Handler1 的摘要说明 /// public class Handler1 : IHttpHandler { private static DictionaryCONNECT_POOL = new Dictionary ();//用户连接池 private static Dictionary > MESSAGE_POOL = new Dictionary >();//离线消息池 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 joinedmsgbuffer = new ArraySegment (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 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 wholemessage= new ArraySegment (new byte[10240]); int i = 0; WebSocketReceiveResult dresult; do { //因为websocket每一次发送的数据会被tcp分包 //所以必须判断接收到的消息是否完整 //不完整就要继续接收并拼接数据包 ArraySegment buffer = new ArraySegment (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 json = (Dictionary )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 ()); // 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客户端代码如下:
webrtc
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) } } } }; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。