如何在Unity(WebGL)与Angular前端间基于Asp.net Core实现音视频聊天?
嘿,我之前刚好折腾过类似的Unity WebGL + WebRTC + SignalR中转的场景,给你几个可行的方向和具体步骤,应该能帮你打通Unity和Angular之间的音视频连接:
一、Unity WebGL端的WebRTC选型与集成
Unity WebGL本质是编译成浏览器JS运行,所以有两种靠谱的实现路径:
1. 用Unity官方WebRTC包(推荐)
Unity官方提供了com.unity.webrtc包,原生适配WebGL平台,比自己写JS交互更稳定。具体步骤:
- 在Unity Package Manager中搜索并安装
com.unity.webrtc,注意选择和你Unity版本兼容的版本(2020+版本兼容性更好)。 - 编写核心逻辑,实现PeerConnection创建、SDP/ICE交换、流处理,通过SignalR和ASP.NET Core中转:
using UnityEngine; using Unity.WebRTC; using TMPro; public class UnityWebRTCClient : MonoBehaviour { private RTCPeerConnection _peerConnection; private MediaStream _localStream; [SerializeField] private RawImage _localVideo; [SerializeField] private RawImage _remoteVideo; async void Start() { // 初始化PeerConnection,配置自有STUN/TURN服务器(满足数据私有要求) var config = new RTCConfiguration { iceServers = new[] { new RTCIceServer { urls = new[] { "stun:your-self-hosted-stun.com:3478" }, username = "your-stun-username", credential = "your-stun-password" } } }; _peerConnection = new RTCPeerConnection(ref config); // 获取本地音视频流(WebGL下需浏览器授权) _localStream = await MediaStream.GetUserMediaAsync(new MediaStreamConstraints { audio = true, video = true }); foreach (var track in _localStream.GetTracks()) { _peerConnection.AddTrack(track, _localStream); } // 绑定本地流到UI var localVideoTexture = new VideoTexture(_localStream.GetVideoTracks()[0]); _localVideo.texture = localVideoTexture; // 监听ICE候选,通过SignalR发送到服务器中转 _peerConnection.OnIceCandidate += (candidate) => { if (candidate != null) { SignalRClient.Instance.SendMessage("SendIceCandidateToClient", SignalRClient.Instance.TargetClientId, candidate.ToJson()); } }; // 监听远程流,渲染到UI _peerConnection.OnTrack += (evt) => { var remoteStream = evt.Streams[0]; var remoteVideoTexture = new VideoTexture(remoteStream.GetVideoTracks()[0]); _remoteVideo.texture = remoteVideoTexture; }; } // 接收来自SignalR的SDP Offer,生成Answer并返回 public async void HandleOffer(string offerJson) { var offer = RTCSessionDescription.Parse(offerJson); await _peerConnection.SetRemoteDescription(ref offer); var answer = await _peerConnection.CreateAnswer(); await _peerConnection.SetLocalDescription(ref answer); SignalRClient.Instance.SendMessage("SendAnswerToClient", SignalRClient.Instance.TargetClientId, answer.ToJson()); } // 接收来自SignalR的ICE候选,添加到PeerConnection public void HandleIceCandidate(string candidateJson) { var candidate = RTCIceCandidate.Parse(candidateJson); _peerConnection.AddIceCandidate(ref candidate); } }
2. 通过JSInterop调用浏览器WebRTC/simple-peer
如果官方包有兼容性问题,可以直接在Unity中通过JS交互调用浏览器的WebRTC API或simple-peer库:
- 在Unity的
Plugins文件夹下创建WebRTCSimplePeer.jslib,封装JS逻辑:
mergeInto(LibraryManager.library, { InitSimplePeer: function(isInitiator) { const peer = new SimplePeer({ initiator: Boolean(isInitiator), trickle: false, config: { iceServers: [{ urls: "stun:your-self-hosted-stun.com:3478" }] } }); // 转发signal事件到Unity peer.on('signal', (data) => { const jsonStr = JSON.stringify(data); const buffer = _malloc(jsonStr.length + 1); stringToUTF8(jsonStr, buffer, jsonStr.length + 1); window.unityInstance.SendMessage('WebRTCHandler', 'OnSignalReceived', buffer); _free(buffer); }); // 转发远程流到Unity peer.on('stream', (stream) => { const url = URL.createObjectURL(stream); const buffer = _malloc(url.length + 1); stringToUTF8(url, buffer, url.length + 1); window.unityInstance.SendMessage('WebRTCHandler', 'OnRemoteStreamReceived', buffer); _free(buffer); }); window.simplePeerInstance = peer; }, SendSignal: function(signalJsonPtr) { const signalJson = UTF8ToString(signalJsonPtr); window.simplePeerInstance.signal(JSON.parse(signalJson)); }, StartLocalStream: function() { navigator.mediaDevices.getUserMedia({ audio: true, video: true }) .then(stream => { window.simplePeerInstance.addStream(stream); const url = URL.createObjectURL(stream); const buffer = _malloc(url.length + 1); stringToUTF8(url, buffer, url.length + 1); window.unityInstance.SendMessage('WebRTCHandler', 'OnLocalStreamReceived', buffer); _free(buffer); }); } });
- 在Unity中编写C#脚本调用JS方法:
using UnityEngine; using System.Runtime.InteropServices; public class WebRTCHandler : MonoBehaviour { [DllImport("__Internal")] private static extern void InitSimplePeer(bool isInitiator); [DllImport("__Internal")] private static extern void SendSignal(string signalJson); [DllImport("__Internal")] private static extern void StartLocalStream(); void Start() { // Unity作为接收方,Angular作为发起方 InitSimplePeer(false); StartLocalStream(); } // 接收JS传来的signal,通过SignalR中转给Angular public void OnSignalReceived(string signalJson) { SignalRClient.Instance.SendMessage("SendSignalToClient", SignalRClient.Instance.TargetClientId, signalJson); } // 接收服务器中转的Angular信号,传给JS的simple-peer public void HandleRemoteSignal(string signalJson) { SendSignal(signalJson); } // 渲染本地/远程流 public void OnLocalStreamReceived(string streamUrl) { GetComponent<RawImage>().texture = new WWW(streamUrl).texture; } public void OnRemoteStreamReceived(string streamUrl) { GameObject.Find("RemoteVideo").GetComponent<RawImage>().texture = new WWW(streamUrl).texture; } }
二、ASP.NET Core SignalR中转层适配
基于你已有的SignalR通信基础,新增Hub方法中转WebRTC信号:
public class WebRTCHub : Hub { // Unity发送ICE候选到服务器,转发给指定Angular客户端 public async Task SendIceCandidateToClient(string targetClientId, string candidateJson) { await Clients.Client(targetClientId).SendAsync("ReceiveIceCandidate", candidateJson); } // Unity发送SDP Answer到服务器,转发给指定Angular客户端 public async Task SendAnswerToClient(string targetClientId, string answerJson) { await Clients.Client(targetClientId).SendAsync("ReceiveAnswer", answerJson); } // Angular发送SDP Offer到服务器,转发给指定Unity客户端 public async Task SendOfferToUnity(string targetUnityClientId, string offerJson) { await Clients.Client(targetUnityClientId).SendAsync("ReceiveOffer", offerJson); } // Angular发送ICE候选到服务器,转发给指定Unity客户端 public async Task SendIceCandidateToUnity(string targetUnityClientId, string candidateJson) { await Clients.Client(targetUnityClientId).SendAsync("ReceiveIceCandidate", candidateJson); } }
记得在Startup.cs中注册这个Hub,和现有SignalR配置兼容。
三、两端流程对齐
不管用哪种方案,都要和Angular端的simple-peer流程保持一致:
- 客户端标识管理:用SignalR的
ConnectionId或业务用户ID标记Unity和Angular客户端,确保信号能精准转发。 - 连接流程:
- Angular作为发起方,生成SDP Offer,通过SignalR发送到服务器→转发给Unity
- Unity接收Offer,生成SDP Answer,通过SignalR回传给服务器→转发给Angular
- 双方交换ICE候选,完成P2P连接建立
- 流处理:Unity端渲染远程音视频流,Angular端保持现有simple-peer的流处理逻辑即可。
四、关键注意事项
- 私有STUN/TURN服务器:为了满足数据留存自有服务器的要求,不要用公共STUN服务器,部署开源的coturn服务器即可。
- WebGL权限:WebGL下获取音视频需要用户授权,确保Unity WebGL模板中包含权限请求逻辑。
- 版本兼容性:Unity WebRTC包要和Unity版本匹配,Angular的simple-peer版本要和JSInterop中使用的版本一致。
内容的提问来源于stack exchange,提问作者Ties Theunissen
相关产品推荐
相关产品推荐

