You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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流程保持一致:

  1. 客户端标识管理:用SignalR的ConnectionId或业务用户ID标记Unity和Angular客户端,确保信号能精准转发。
  2. 连接流程:
    • Angular作为发起方,生成SDP Offer,通过SignalR发送到服务器→转发给Unity
    • Unity接收Offer,生成SDP Answer,通过SignalR回传给服务器→转发给Angular
    • 双方交换ICE候选,完成P2P连接建立
  3. 流处理:Unity端渲染远程音视频流,Angular端保持现有simple-peer的流处理逻辑即可。
四、关键注意事项
  • 私有STUN/TURN服务器:为了满足数据留存自有服务器的要求,不要用公共STUN服务器,部署开源的coturn服务器即可。
  • WebGL权限:WebGL下获取音视频需要用户授权,确保Unity WebGL模板中包含权限请求逻辑。
  • 版本兼容性:Unity WebRTC包要和Unity版本匹配,Angular的simple-peer版本要和JSInterop中使用的版本一致。

内容的提问来源于stack exchange,提问作者Ties Theunissen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:43:16