C#|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{/// /// 离线消息/// 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 Dictionary CONNECT_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); //移除离线消息}#endregionwhile (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; }#endregionfor (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 - 锐客网 #yours {width: 200px; position: absolute; top: 50px; left: 100px; }#theirs {width: 600px; position: absolute; top: 50px; left: 400px; }
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 = https://www.it610.com/article/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); }; //errorwebsocket.onerror = function (e) {console.log('error:' + e.data); }; //收到消息websocket.onmessage = (event) => {if (event.data =https://www.it610.com/article/="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)}}}}; }
【C#|C# WebApi+Webrtc局域网音视频通话实例】以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
推荐阅读
- 音视频开发|音视频开发 三(渲染图片纹理)
- 【音视频基础】(七)(CIE颜色空间三之从CIE|【音视频基础】(七):CIE颜色空间三之从CIE XYZ到CIE xyY)
- 音视频|短视频运营(如何提高自己的剪辑技巧())
- 云信小课堂|如何实现音视频安全检测()
- 阿里云视频云「|阿里云视频云「 vPaaS 」演绎了怎样的音视频应用开发「未来图景」()
- 音视频人才的需求从小众变成了大众(一名合格的音视频开发人员,少则30万起,多则年薪可达百万以上......)
- 牛赞(音视频前端跨平台技术应用)
- 九、Uniapp+vue+腾讯IM+腾讯音视频开发仿微信IM,支持各类消息收发,音视频通话,附vue源码-聊天会话管理的实现
- 八、Uniapp+vue+腾讯IM+腾讯音视频开发仿微信IM,各类消息收发,音视频通话,附vue源码-聊天输入框扩展面板的实现
- 拍乐云首发音视频「分组讨论」开放能力,开启线上群聊互动新玩法