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)
-
- }
- }
- }
- };
- }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。