using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Unity.RenderStreaming { /// /// Host模式的连接处理器 /// 适用于私有模式下Unity作为Host(第一个加入房间的客户端) /// 支持多个Participant连接,每个Participant拥有独立的PeerConnection /// /// 工作原理: /// - Host通过CreateConnection(connectionId)创建房间连接 /// - 当Participant加入时,服务器发送participant-joined通知 /// - WebSocketSignaling将participantId作为内部connectionId使用, /// 因此每个Participant会自动创建独立的PeerConnection /// - 当收到Participant的offer时,自动添加Stream并发送answer /// - 当Participant离开时,自动清理对应的连接资源 /// [AddComponentMenu("Render Streaming/Host Connection")] public class HostConnection : SignalingHandlerBase, IOfferHandler, IAddChannelHandler, IDisconnectHandler, IDeletedConnectionHandler, IAddReceiverHandler, IParticipantJoinedHandler, IParticipantLeftHandler, ICallRequestHandler, IMessageHandler { [SerializeField] private List streams = new List(); /// /// 当前连接的所有Participant的participantId列表 /// private List participantIds = new List(); /// /// 房间的connectionId(Host创建连接时使用的ID) /// private string roomConnectionId; public override IEnumerable Streams => streams; public void AddComponent(Component component) { streams.Add(component); } public void RemoveComponent(Component component) { streams.Remove(component); } /// /// 创建Host连接(即创建房间) /// Unity作为第一个连接的客户端,服务器会分配host角色(polite=false) /// /// 房间ID public override void CreateConnection(string connectionId) { roomConnectionId = connectionId; base.CreateConnection(connectionId); } /// /// 删除Host连接(关闭房间) /// Host离开时,服务器会自动断开所有Participant /// public override void DeleteConnection(string connectionId) { // 清理所有Participant连接 foreach (var participantId in participantIds.ToList()) { DisconnectParticipant(participantId); } participantIds.Clear(); if (connectionId == roomConnectionId) { base.DeleteConnection(connectionId); roomConnectionId = null; } else { base.DeleteConnection(connectionId); } } public void OnDeletedConnection(SignalingEventData eventData) { Disconnect(eventData.connectionId); } public void OnDisconnect(SignalingEventData eventData) { Disconnect(eventData.connectionId); } private void Disconnect(string connectionId) { if (!participantIds.Contains(connectionId) && connectionId != roomConnectionId) return; participantIds.Remove(connectionId); foreach (var sender in streams.OfType()) { RemoveSender(connectionId, sender); } foreach (var receiver in streams.OfType()) { RemoveReceiver(connectionId, receiver); } foreach (var channel in streams.OfType().Where(c => c.ConnectionId == connectionId)) { RemoveChannel(connectionId, channel); } } /// /// 断开指定Participant的连接资源 /// private void DisconnectParticipant(string participantId) { foreach (var sender in streams.OfType()) { RemoveSender(participantId, sender); } foreach (var receiver in streams.OfType()) { RemoveReceiver(participantId, receiver); } foreach (var channel in streams.OfType().Where(c => c.ConnectionId == participantId)) { RemoveChannel(participantId, channel); } } /// /// 收到Participant的offer时触发 /// WebSocketSignaling已经将participantId作为内部connectionId /// 所以每个Participant的offer会自动创建独立的PeerConnection /// public void OnOffer(SignalingEventData data) { // 记录新的Participant连接 if (!participantIds.Contains(data.connectionId)) { participantIds.Add(data.connectionId); Debug.Log($"[HostConnection] Participant offer received: {data.connectionId}"); } // 为该Participant添加所有Stream foreach (var source in streams.OfType()) { AddSender(data.connectionId, source); } foreach (var channel in streams.OfType().Where(c => c.IsLocal)) { AddChannel(data.connectionId, channel); } // 发送answer给该Participant SendAnswer(data.connectionId); } /// /// 收到远程Track时触发 /// public void OnAddReceiver(SignalingEventData data) { var track = data.transceiver.Receiver.Track; IStreamReceiver receiver = GetReceiver(track.Kind); SetReceiver(data.connectionId, receiver, data.transceiver); } /// /// 收到远程DataChannel时触发 /// public void OnAddChannel(SignalingEventData data) { var channel = streams.OfType(). FirstOrDefault(r => !r.IsConnected && !r.IsLocal); channel?.SetChannel(data.connectionId, data.channel); } /// /// 新Participant加入房间通知 /// 在私有模式下,服务器会通知Host有新的Participant加入 /// 此时不做额外处理,等收到Participant的offer时再建立连接 /// public void OnParticipantJoined(SignalingEventData eventData) { Debug.Log($"[HostConnection] Participant joined: connectionId={eventData.connectionId}, participantId={eventData.participantId}"); } /// /// Participant离开房间通知 /// 清理该Participant的连接资源 /// public void OnParticipantLeft(SignalingEventData eventData) { string participantId = eventData.participantId; if (!string.IsNullOrEmpty(participantId)) { Debug.Log($"[HostConnection] Participant left: connectionId={eventData.connectionId}, participantId={participantId}"); DisconnectParticipant(participantId); participantIds.Remove(participantId); } } /// /// 收到呼叫请求 /// public void OnCallRequest(SignalingEventData eventData) { Debug.Log($"[HostConnection] Call request from: {eventData.connectionId}, data: {eventData.message}"); } /// /// 收到自定义消息 /// public void OnMessage(SignalingEventData eventData) { Debug.Log($"[HostConnection] Message from: {eventData.connectionId}, participantId: {eventData.participantId}, message: {eventData.message}"); } /// /// 获取当前连接的Participant数量 /// public int ParticipantCount => participantIds.Count; /// /// 获取所有Participant的ID列表 /// public IReadOnlyList ParticipantIds => participantIds.AsReadOnly(); IStreamReceiver GetReceiver(WebRTC.TrackKind kind) { if (kind == WebRTC.TrackKind.Audio) return streams.OfType().FirstOrDefault(); if (kind == WebRTC.TrackKind.Video) return streams.OfType().FirstOrDefault(); throw new System.ArgumentException(); } } }