C# WebApi+Webrtc局域網(wǎng)音視頻通話實(shí)例
C# WebApi+Webrtc 局域網(wǎng)音視頻通話示例,供大家參考,具體內(nèi)容如下
本示例通過(guò)IIS部署webapi,利用websocket進(jìn)行webrtc消息交換,通過(guò)Chrome瀏覽器訪問(wèn),可實(shí)現(xiàn)局域網(wǎng)內(nèi)webrtc 音視頻通話。
通過(guò)Chrome瀏覽器打開(kāi)localhost/live.html本地網(wǎng)址,打開(kāi)兩個(gè)本地網(wǎng),點(diǎn)擊任意頁(yè)面連接按鈕即聯(lián)通。
本示例未實(shí)現(xiàn)NAT穿透處理,互聯(lián)網(wǎng)無(wú)法聯(lián)通,如需NAT穿透請(qǐng)自行查閱相關(guān)資料。
關(guān)于webrtc、webapi相關(guān)技術(shù)說(shuō)明請(qǐng)自行查閱相關(guān)資料,本文不做贅述說(shuō)明。
運(yùn)行效果如下圖:

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 的摘要說(shuō)明
/// </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時(shí),添加到連接池中
if (!CONNECT_POOL.ContainsKey(user))
{
CONNECT_POOL.Add(user, socket);//不存在,添加
}
else
{
if (socket != CONNECT_POOL[user])//當(dāng)前對(duì)象不一致,更新
{
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
{
//因?yàn)閣ebsocket每一次發(fā)送的數(shù)據(jù)會(huì)被tcp分包
//所以必須判斷接收到的消息是否完整
//不完整就要繼續(xù)接收并拼接數(shù)據(jù)包
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 消息處理(字符截取、消息轉(zhuǎn)發(fā))
try
{
#region 關(guān)閉Socket處理,刪除連接池
if (socket.State != WebSocketState.Open)//連接關(guān)閉
{
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)
{
//消息轉(zhuǎn)發(fā)異常處理,本次消息忽略 繼續(xù)監(jiān)聽(tīng)接下來(lái)的消息
}
#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() {
//發(fā)送offer和answer的函數(shù),發(fā)送本地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端請(qǐng)求關(guān)閉
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) {
//如果是一個(gè)ICE的候選,則將其加入到PeerConnection中,否則設(shè)定對(duì)方的session描述為傳遞過(guò)來(lái)的描述
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)
}
}
}
};
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#通過(guò)html調(diào)用WinForm的方法
這篇文章主要介紹了C#通過(guò)html調(diào)用WinForm的方法,涉及html頁(yè)面中使用JavaScript訪問(wèn)C#的相關(guān)技巧,需要的朋友可以參考下2016-04-04
WPF實(shí)現(xiàn)在線預(yù)覽和顯示W(wǎng)ord和PDF文件
這篇文章主要為大家詳細(xì)介紹了如何使用WPF實(shí)現(xiàn)在線預(yù)覽和顯示W(wǎng)ord和PDF文件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02
unity實(shí)現(xiàn)簡(jiǎn)單抽獎(jiǎng)系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了unity實(shí)現(xiàn)簡(jiǎn)單抽獎(jiǎng)系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02

