经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
C# WebApi+Webrtc局域网音视频通话实例
来源:jb51  时间:2021/7/26 14:07:29  对本文有异议

C# WebApi+Webrtc 局域网音视频通话示例,供大家参考,具体内容如下

本示例通过IIS部署webapi,利用websocket进行webrtc消息交换,通过Chrome浏览器访问,可实现局域网内webrtc 音视频通话。

通过Chrome浏览器打开localhost/live.html本地网址,打开两个本地网,点击任意页面连接按钮即联通。

本示例未实现NAT穿透处理,互联网无法联通,如需NAT穿透请自行查阅相关资料。

关于webrtc、webapi相关技术说明请自行查阅相关资料,本文不做赘述说明。

运行效果如下图:

webapi端Handler1.ashx代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net.WebSockets;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using System.Web;
  9. using System.Web.WebSockets;
  10.  
  11. namespace webrtclan
  12. {
  13. /// <summary>
  14. /// 离线消息
  15. /// </summary>
  16. public class MessageInfo
  17. {
  18. public MessageInfo(DateTime _MsgTime, ArraySegment<byte> _MsgContent)
  19. {
  20. MsgTime = _MsgTime;
  21. MsgContent = _MsgContent;
  22. }
  23. public DateTime MsgTime { get; set; }
  24. public ArraySegment<byte> MsgContent { get; set; }
  25. }
  26.  
  27. /// <summary>
  28. /// Handler1 的摘要说明
  29. /// </summary>
  30. public class Handler1 : IHttpHandler
  31. {
  32. private static Dictionary<string, WebSocket> CONNECT_POOL = new Dictionary<string, WebSocket>();//用户连接池
  33. private static Dictionary<string, List<MessageInfo>> MESSAGE_POOL = new Dictionary<string, List<MessageInfo>>();//离线消息池
  34.  
  35. public void ProcessRequest(HttpContext context)
  36. {
  37. if (context.IsWebSocketRequest)
  38. {
  39. context.Response.ContentType = "application/json";
  40. context.Response.Charset = "utf-8";
  41. context.AcceptWebSocketRequest(ProcessMsg);
  42. }
  43. }
  44.  
  45. private async Task ProcessMsg(AspNetWebSocketContext context)
  46. {
  47. WebSocket socket = context.WebSocket;
  48. string user = context.QueryString["user"].ToString();
  49. try
  50. {
  51. #region 用户添加连接池
  52. //第一次open时,添加到连接池中
  53. if (!CONNECT_POOL.ContainsKey(user))
  54. {
  55. CONNECT_POOL.Add(user, socket);//不存在,添加
  56. }
  57. else
  58. {
  59. if (socket != CONNECT_POOL[user])//当前对象不一致,更新
  60. {
  61. CONNECT_POOL[user] = socket;
  62. }
  63. }
  64. #endregion
  65.  
  66. //#region 连线成功
  67. //for (int cp = 0; cp < CONNECT_POOL.Count; cp++)
  68. //{
  69. // if (CONNECT_POOL.ElementAt(cp).Key != user)
  70. // {
  71. // string joinedmsg = "{\"FROM\":\"" + user + "\",\"event\":\"joined\"}";
  72. // ArraySegment<byte> joinedmsgbuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(joinedmsg));
  73. // WebSocket destSocket = CONNECT_POOL.ElementAt(cp).Value;//目的客户端
  74. // await destSocket.SendAsync(joinedmsgbuffer, WebSocketMessageType.Text, true, CancellationToken.None);
  75. // }
  76. //}
  77. //#endregion
  78.  
  79. #region 离线消息处理
  80. if (MESSAGE_POOL.ContainsKey(user))
  81. {
  82. List<MessageInfo> msgs = MESSAGE_POOL[user];
  83. foreach (MessageInfo item in msgs)
  84. {
  85. await socket.SendAsync(item.MsgContent, WebSocketMessageType.Text, true, CancellationToken.None);
  86. }
  87. MESSAGE_POOL.Remove(user);//移除离线消息
  88. }
  89. #endregion
  90. while (true)
  91. {
  92. if (socket.State == WebSocketState.Open)
  93. {
  94. ArraySegment<byte> wholemessage= new ArraySegment<byte>(new byte[10240]);
  95.  
  96. int i = 0;
  97.  
  98. WebSocketReceiveResult dresult;
  99. do
  100. {
  101. //因为websocket每一次发送的数据会被tcp分包
  102. //所以必须判断接收到的消息是否完整
  103. //不完整就要继续接收并拼接数据包
  104. ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
  105. dresult = await socket.ReceiveAsync(buffer, CancellationToken.None);
  106. string message1 = Encoding.UTF8.GetString(buffer.Array);
  107. buffer.Array.CopyTo(wholemessage.Array,i);
  108. i += 2048;
  109. } while (false == dresult.EndOfMessage);
  110.  
  111. //string message = Encoding.UTF8.GetString(wholemessage.Array);
  112. //message = message.Replace("\0", "").Trim();
  113. //JavaScriptSerializer serializer = new JavaScriptSerializer();
  114. //Dictionary<string, object> json = (Dictionary<string, object>)serializer.DeserializeObject(message);
  115. //string target = (string)json.ElementAt(1).Value;
  116.  
  117. #region 消息处理(字符截取、消息转发)
  118. try
  119. {
  120. #region 关闭Socket处理,删除连接池
  121. if (socket.State != WebSocketState.Open)//连接关闭
  122. {
  123. if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//删除连接池
  124. break;
  125. }
  126. #endregion
  127. for (int cp = 0; cp < CONNECT_POOL.Count; cp++)
  128. {
  129. //if (CONNECT_POOL.ElementAt(cp).Key!=target)
  130. // {
  131. WebSocket destSocket = CONNECT_POOL.ElementAt(cp).Value;//目的客户端
  132. await destSocket.SendAsync(wholemessage, WebSocketMessageType.Text, true, CancellationToken.None);
  133. // }
  134. }
  135.  
  136. //if (CONNECT_POOL.ContainsKey(descUser))//判断客户端是否在线
  137. //{
  138. // WebSocket destSocket = CONNECT_POOL[descUser];//目的客户端
  139. // if (destSocket != null && destSocket.State == WebSocketState.Open)
  140. // await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
  141. //}
  142. //else
  143. //{
  144. // _ = Task.Run(() =>
  145. // {
  146. // if (!MESSAGE_POOL.ContainsKey(descUser))//将用户添加至离线消息池中
  147. // MESSAGE_POOL.Add(descUser, new List<MessageInfo>());
  148. // MESSAGE_POOL[descUser].Add(new MessageInfo(DateTime.Now, buffer));//添加离线消息
  149. // });
  150. //}
  151. }
  152. catch (Exception exs)
  153. {
  154. //消息转发异常处理,本次消息忽略 继续监听接下来的消息
  155. }
  156. #endregion
  157. }
  158. else
  159. {
  160. if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//删除连接池
  161. break;
  162. }
  163. }//while end
  164. }
  165. catch (Exception ex)
  166. {
  167. //整体异常处理
  168. if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);
  169. }
  170. }
  171.  
  172.  
  173. public bool IsReusable
  174. {
  175. get
  176. {
  177. return false;
  178. }
  179. }
  180.  
  181.  
  182. }
  183. }

live.html客户端代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>webrtc</title>
  8. <style>
  9. #yours {
  10. width: 200px;
  11. position: absolute;
  12. top: 50px;
  13. left: 100px;
  14. }
  15. #theirs {
  16. width: 600px;
  17. position: absolute;
  18. top: 50px;
  19. left: 400px;
  20. }
  21. </style>
  22. </head>
  23. <body>
  24. <button onclick="createOffer()">建立连接</button>
  25. <video id="yours" autoplay controls="controls" ></video>
  26. <video id="theirs" autoplay controls="controls"></video>
  27.  
  28. </body>
  29.  
  30. <script src="webrtc.js"></script>
  31.  
  32. </html>

webrtc.js脚本代码如下:

  1. var websocket;
  2.  
  3. function randomNum(minNum, maxNum) {
  4. switch (arguments.length) {
  5. case 1:
  6. return parseInt(Math.random() * minNum + 1, 10);
  7. break;
  8. case 2:
  9. return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
  10. break;
  11. default:
  12. return 0;
  13. break;
  14. }
  15. }
  16. const userid = 'user' + randomNum(0, 100000);
  17.  
  18. function hasUserMedia() {
  19. navigator.getUserMedia = navigator.getUserMedia || navigator.msGetUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
  20. return !!navigator.getUserMedia;
  21. }
  22. function hasRTCPeerConnection() {
  23. window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;
  24. return !!window.RTCPeerConnection;
  25. }
  26.  
  27. var yourVideo = document.getElementById("yours");
  28. var theirVideo = document.getElementById("theirs");
  29. var Connection;
  30.  
  31.  
  32. function startPeerConnection() {
  33. //return;
  34. var config = {
  35. 'iceServers': [
  36. //{ 'urls': 'stun:stun.xten.com:3478' },
  37. //{ 'urls': 'stun:stun.voxgratia.org:3478' },
  38.  
  39. //{ 'url': 'stun:stun.l.google.com:19302' }
  40. ]
  41. };
  42. config = {
  43. iceServers: [
  44. //{ urls: 'stun:stun.l.google.com:19302' },
  45. //{ urls: 'stun:global.stun.twilio.com:3478?transport=udp' }
  46. ]
  47. //sdpSemantics: 'unified-plan'
  48. };
  49. // {
  50. // "iceServers": [{
  51. // "url": "stun:stun.1.google.com:19302"
  52. // }]
  53. // };
  54. Connection = new RTCPeerConnection(config);
  55. Connection.onicecandidate = function (e) {
  56. console.log('onicecandidate');
  57. if (e.candidate) {
  58. websocket.send(JSON.stringify({
  59. "userid": userid,
  60. "event": "_ice_candidate",
  61. "data": {
  62. "candidate": e.candidate
  63. }
  64. }));
  65. }
  66. };
  67. Connection.onaddstream = function (e) {
  68. console.log('onaddstream');
  69. //theirVideo.src = window.URL.createObjectURL(e.stream);
  70. theirVideo.srcObject = e.stream;
  71. };
  72. Connection.onclose = function (e) {
  73. console.log('RTCPeerConnection close'+e);
  74. };
  75. }
  76.  
  77. createSocket();
  78. startPeerConnection();
  79.  
  80. if (hasUserMedia()) {
  81. navigator.getUserMedia({ video: true, audio: true },
  82. stream => {
  83. yourVideo.srcObject = stream;
  84. window.stream = stream;
  85. yourVideo.muted = true;
  86. Connection.addStream(stream)
  87. },
  88. err => {
  89. console.log(err);
  90. })
  91. }
  92.  
  93.  
  94. function createOffer() {
  95. //发送offer和answer的函数,发送本地session描述
  96. Connection.createOffer().then(offer => {
  97. Connection.setLocalDescription(offer);
  98. websocket.send(JSON.stringify({
  99. "userid": userid,
  100. "event": "offer",
  101. "data": {
  102. "sdp": offer
  103. }
  104. }));
  105. });
  106. }
  107.  
  108.  
  109. function createSocket() {
  110. //websocket = null;
  111. websocket = new WebSocket('ws://localhost:80/Handler1.ashx?user='+userid);//('wss://www.ecoblog.online/wss');
  112. eventBind();
  113. };
  114.  
  115. function eventBind() {
  116. //连接成功
  117. websocket.onopen = function (e) {
  118. console.log('open:' + e);
  119. };
  120. //server端请求关闭
  121. websocket.onclose = function (e) {
  122. console.log('close:' + e);
  123. };
  124. //error
  125. websocket.onerror = function (e) {
  126. console.log('error:' + e.data);
  127. };
  128. //收到消息
  129. websocket.onmessage = (event) => {
  130. if (event.data == "new user") {
  131. location.reload();
  132. } else {
  133. var js = event.data.replace(/[\u0000-\u0019]+/g, "");
  134. var json = JSON.parse(js);
  135. if (json.userid != userid) {
  136. //如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
  137. if (json.event === "_ice_candidate" && json.data.candidate) {
  138. Connection.addIceCandidate(new RTCIceCandidate(json.data.candidate));
  139. }
  140. else if (json.event === 'offer') {
  141. Connection.setRemoteDescription(json.data.sdp);
  142. Connection.createAnswer().then(answer => {
  143. Connection.setLocalDescription(answer);
  144. //console.log(window.stream)
  145. websocket.send(JSON.stringify({
  146. "userid": userid,
  147. "event": "answer",
  148. "data": {
  149. "sdp": answer
  150. }
  151. }));
  152. })
  153. }
  154. else if (json.event === 'answer') {
  155. Connection.setRemoteDescription(json.data.sdp);
  156. //console.log(window.stream)
  157.  
  158. }
  159. }
  160. }
  161. };
  162. }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号