This commit is contained in:
2026-05-24 22:26:38 +08:00
parent 9b6ed2bcd6
commit 33128ea686
20 changed files with 380 additions and 392 deletions

View File

@@ -1,4 +1,5 @@
using Stary.Evo;
using Script;
using Stary.Evo;
using Stary.Evo.UIFarme;
namespace RenderStreaming
@@ -12,6 +13,7 @@ namespace RenderStreaming
RegisterSystem<IPanelSystem>(new PanelSystem(new ResourcesAssetLoader()));
RegisterSystem<IGlobalConfigSystem>(new GlobalConfigSystem());
RegisterSystem<ITimerSystem>(new TimerSystem());
RegisterSystem<IRenderStreamingSystem>(new RenderStreamingSystem());
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Script
private Text _confirmBtnTimeText;
private Button _confirmButton;
private bool _isCountingDown;
private MeetingContacts _meetingContacts;
private MeetingContactsController _meetingContactsController;
private Transform _messageText;
private Transform _nameText;
private MainPanel.UsersItem _usersItem;
@@ -33,10 +33,10 @@ namespace Script
return MainArchitecture.Interface;
}
public void SetData(MainPanel.UsersItem item, MeetingContacts meetingContactsController)
public void SetData(MainPanel.UsersItem item, MeetingContactsController meetingContactsControllerController)
{
_usersItem = item;
_meetingContacts = meetingContactsController;
_meetingContactsController = meetingContactsControllerController;
_background = transform.Find("headBackground/image").GetComponent<Image>();
_backgroundName = transform.Find("headBackground/Name").GetComponent<Text>();
@@ -120,9 +120,9 @@ namespace Script
Debug.Log($"点击了联系人: {_usersItem.name}");
// 这里可以添加点击联系人后的逻辑,比如打开聊天窗口
// 或者发送邀请请求等
_meetingContacts.ClickContactEntry(_background.GetComponent<Image>().sprite,
_meetingContactsController.ClickContactEntry(_background.GetComponent<Image>().sprite,
_backgroundName.GetComponent<Text>().text, _usersItem);
_meetingContacts.CurrentEntry = this;
_meetingContactsController.CurrentEntry = this;
}
public void StartCountdown()

View File

@@ -28,17 +28,17 @@ namespace Script
/// <summary>
/// 会议联系人类和面板
/// </summary>
private MeetingContacts _meetingContacts;
private MeetingContactsController _meetingContactsController;
/// <summary>
/// 会议参与者列表和面板
/// </summary>
private MeetingInfoList _meetingInfoList;
private MeetingInfoListController _meetingInfoListController;
/// <summary>
/// 会议聊天面板
/// </summary>
private MeetingChat _meetingChat;
private MeetingChatController _meetingChatController;
/// <summary>
/// 房间人数
@@ -80,18 +80,18 @@ namespace Script
_contactsTog = panelGo.transform.Find("Tabs/Contacts").GetComponent<Toggle>();
_meetingOrganizersTog = panelGo.transform.Find("Tabs/MeetingOrganizers").GetComponent<Toggle>();
_meetingContacts = new MeetingContacts();
_meetingContacts.Initialize(panelGo.transform.Find("MeetingContacts").gameObject);
_meetingInfoList = new MeetingInfoList();
_meetingInfoList.Initialize(panelGo.transform.Find("MeetingInfoList").gameObject, this);
_meetingChat = new MeetingChat();
_meetingChat.Initialize(panelGo.transform.Find("MeetingChat").gameObject, this);
_meetingContactsController = new MeetingContactsController();
_meetingContactsController.Initialize(panelGo.transform.Find("MeetingContacts").gameObject);
_meetingInfoListController = new MeetingInfoListController();
_meetingInfoListController.Initialize(panelGo.transform.Find("MeetingInfoList").gameObject, this);
_meetingChatController = new MeetingChatController();
_meetingChatController.Initialize(panelGo.transform.Find("MeetingChat").gameObject, this);
}
public override void OnEnter(Action complete = null)
{
base.OnEnter(complete);
_meetingInfoList.OnEnter();
_meetingInfoListController.OnEnter();
_titleText.text = this.GetSystem<IGlobalConfigSystem>().GetConnectionName();
this.GetSystem<IGlobalConfigSystem>().StartConnectionTime(_timeText);
_idText.text = this.GetSystem<IGlobalConfigSystem>().GetConnectionId();
@@ -107,7 +107,7 @@ namespace Script
public override void OnExit(float delay = 0)
{
base.OnExit(delay);
_meetingInfoList.OnExit();
_meetingInfoListController.OnExit();
_arrowLeft.onClick.RemoveListener(OnArrowLeftClick);
_chatTog.onValueChanged.RemoveListener(OnChatTog);
_contactsTog.onValueChanged.RemoveListener(OnContactsTog);
@@ -131,7 +131,7 @@ namespace Script
}
// 更新会议聊天面板人数
_meetingChat.OnUserCountChangedEvent?.Invoke(obj.Count);
_meetingChatController.OnUserCountChangedEvent?.Invoke(obj.Count);
}
private void OnUsersChangedDisconnected(string obj)
@@ -152,26 +152,26 @@ namespace Script
}
_roomPeopleNumText.text = _userMap.Count.ToString();
_meetingChat.OnUserCountChangedEvent?.Invoke(_userMap.Count);
_meetingChatController.OnUserCountChangedEvent?.Invoke(_userMap.Count);
}
private void OnMeetingOrganizersTog(bool value)
{
_meetingInfoList.PanelGo.SetActive(value);
_meetingInfoListController.PanelGo.SetActive(value);
}
private void OnContactsTog(bool value)
{
_meetingContacts.PanelGo.SetActive(value);
_meetingContactsController.PanelGo.SetActive(value);
if (value)
_meetingContacts.OnEnter();
_meetingContactsController.OnEnter();
}
private void OnChatTog(bool value)
{
_meetingChat.PanelGo.SetActive(value);
_meetingChatController.PanelGo.SetActive(value);
if (value)
_meetingChat.OnEnter();
_meetingChatController.OnEnter();
}
private void OnArrowLeftClick()
@@ -181,8 +181,8 @@ namespace Script
public override void OnDestroy()
{
base.OnDestroy();
_meetingChat.OnDestroy();
_meetingContacts.OnDestroy();
_meetingChatController.OnDestroy();
_meetingContactsController.OnDestroy();
}
[Serializable]

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using RenderStreaming;
using Script.Util;
@@ -10,7 +11,7 @@ using UnityEngine.UI;
namespace Script
{
public class MeetingChat : IController
public class MeetingChatController : IController
{
public GameObject PanelGo;
private MainPanel _mainPanel;
@@ -62,6 +63,9 @@ namespace Script
_objectLeftPool.AllRelease();
}
/// <summary>
/// 发送聊天信息
/// </summary>
private void OnSendButtonClick()
{
if (string.IsNullOrEmpty(_messageInput.text)) return;
@@ -97,6 +101,9 @@ namespace Script
SignalingMessageHelper.SendMessage(JsonConvert.SerializeObject(send));
_messageInput.text = "";
chatData.isSelf = !chatData.isSelf;
var utc = DateTimeOffset.Parse(chatData.timestamp, CultureInfo.InvariantCulture);
var local = utc.ToLocalTime().ToString("HH:mm");
chatData.timestamp = local;
_chatDatas.Add(chatData);
var entry = _objectRightPool.Get();
SetMessageEntry(chatData, entry);
@@ -119,7 +126,7 @@ namespace Script
var name = entry.transform.Find("name");
var message = entry.transform.Find("message");
var sprite = entry.transform.Find("sprite");
var time = entry.transform.Find("message/messagebg/time");
if (string.IsNullOrEmpty(data.senderAvatar))
{
var randomColor = WebRTCUtil.GetRandomColor();
@@ -148,6 +155,11 @@ namespace Script
new Rect(0, 0, texture.width, texture.height),
Vector2.one);
}
var utc = DateTimeOffset.Parse(data.timestamp, CultureInfo.InvariantCulture);
var local = utc.ToLocalTime().ToString("HH:mm");
data.timestamp = local;
time.GetComponent<Text>().text = data.timestamp;
}
private void OnUserCountChanged(int obj)

View File

@@ -9,7 +9,7 @@ using UnityEngine.UI;
namespace Script
{
public class MeetingContacts : IController
public class MeetingContactsController : IController
{
/// <summary>
/// 联系人列表项字典

View File

@@ -1,14 +1,16 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using RenderStreaming;
using Script.Util;
using Stary.Evo;
using Stary.Evo.UIFarme;
using Unity.RenderStreaming;
using UnityEngine;
using UnityEngine.UI;
namespace Script
{
public class MeetingInfoList : IController
public class MeetingInfoListController : IController
{
private MainPanel _mainPanel;
public GameObject PanelGo;
@@ -29,6 +31,15 @@ namespace Script
private GameObjectPool _objectPool;
private Toggle _micTog;
private Toggle _videoTog;
private Toggle _recordTog;
private Button _hangUpTog;
private AudioStreamSender _audioStreamSender;
private VideoStreamSender _videoStreamSender;
public void Initialize(GameObject panelGo, MainPanel mainPanel)
{
PanelGo = panelGo;
@@ -37,18 +48,88 @@ namespace Script
_content = panelGo.transform.Find("MeetingGrid/Viewport/Content");
_meetingNum = panelGo.transform.Find("MeetingNum/Num").GetComponent<Text>();
_objectPool = PanelGo.transform.Find("MeetingGrid/Pool").GetComponent<GameObjectPool>();
_micTog = PanelGo.transform.Find("menuBar/mic").GetComponent<Toggle>();
_videoTog = PanelGo.transform.Find("menuBar/video").GetComponent<Toggle>();
_recordTog = PanelGo.transform.Find("menuBar/record").GetComponent<Toggle>();
_hangUpTog = PanelGo.transform.Find("menuBar/hangUp").GetComponent<Button>();
_audioStreamSender = GameObject.Find("RenderStreaming").GetComponent<AudioStreamSender>();
_videoStreamSender = GameObject.Find("RenderStreaming").GetComponent<VideoStreamSender>();
}
public void OnEnter()
{
LoadUsers();
_micTog.onValueChanged.AddListener(OnMicTogValueChanged);
_videoTog.onValueChanged.AddListener(OnVideoTogValueChanged);
_recordTog.onValueChanged.AddListener(OnRecordTogValueChanged);
_hangUpTog.onClick.AddListener(OnHangUpTogValueChanged);
GameObject.FindObjectOfType<MessageChannel>().OnMediaStateChangeReceived += OnMediaStateChangeReceived;
GameObject.FindObjectOfType<MessageChannel>().OnUserInfoMessageReceived += OnUserInfoMessageReceived;
GameObject.FindObjectOfType<HostConnection>().OnParticipantDisconnected += OnParticipantDisconnected;
}
/// <summary>
private void OnMicTogValueChanged(bool value)
{
var message = new
{
type = "on-message",
data = new
{
connectionId = this.GetSystem<IGlobalConfigSystem>().GetConnectionId(),
message = new
{
type = "media-state-changed",
data = new
{
userId = this.GetSystem<IGlobalConfigSystem>().GetUserId(),
audio = value,
video = _videoTog.isOn
}
}
}
};
_audioStreamSender.enabled = value;
SignalingMessageHelper.SendMessage(JsonConvert.SerializeObject(message));
}
private void OnVideoTogValueChanged(bool value)
{
var message = new
{
type = "on-message",
data = new
{
connectionId = this.GetSystem<IGlobalConfigSystem>().GetConnectionId(),
message = new
{
type = "media-state-changed",
data = new
{
userId = this.GetSystem<IGlobalConfigSystem>().GetUserId(),
audio = _micTog.isOn,
video = value
}
}
}
};
_videoStreamSender.enabled = value;
SignalingMessageHelper.SendMessage(JsonConvert.SerializeObject(message));
}
private void OnRecordTogValueChanged(bool value)
{
}
private void OnHangUpTogValueChanged()
{
this.GetSystem<IRenderStreamingSystem>().HangUp();
this.GetSystem<IRenderStreamingSystem>().SetLocalVideoImage(null);
this.GetSystem<IPanelSystem>().PopQueue<MainPanel>();
this.GetSystem<IPanelSystem>().PushQueue<StartPanel>();
}
/// <summary>
/// 当有用户断开连接时调用
/// </summary>
/// <param name="id"></param>
@@ -225,6 +306,9 @@ namespace Script
GameObject.FindObjectOfType<MessageChannel>().OnMediaStateChangeReceived -= OnMediaStateChangeReceived;
GameObject.FindObjectOfType<MessageChannel>().OnUserInfoMessageReceived -= OnUserInfoMessageReceived;
GameObject.FindObjectOfType<HostConnection>().OnParticipantDisconnected -= OnParticipantDisconnected;
foreach (var info in _meetingList.Values) _objectPool.Release(info.participant);
_meetingList.Clear();
}
public void Destroy()

View File

@@ -1,11 +0,0 @@
using RenderStreaming;
using Stary.Evo;
using UnityEngine;
public class MeetingChatController : MonoBehaviour, IController
{
public IArchitecture GetArchitecture()
{
return MainArchitecture.Interface;
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: cd96585787c8a4d42b3ad979cf3e31e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,25 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using RenderStreaming;
using Stary.Evo;
using UnityEngine;
public class MeetingContactsController : MonoBehaviour ,IController
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public IArchitecture GetArchitecture()
{
return MainArchitecture.Interface;
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 19625967ea163c04095b4ef20880146a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,35 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using RenderStreaming;
using Stary.Evo;
using UnityEngine;
public class MeetingInfoList : MonoBehaviour, IController
{
public Dictionary<GameObject, MeetingInfo> Meetings;
// Start is called before the first frame update
void Start()
{
Meetings = new Dictionary<GameObject, MeetingInfo>();
}
// Update is called once per frame
void Update()
{
}
public IArchitecture GetArchitecture()
{
return MainArchitecture.Interface;
}
public class MeetingInfo
{
public Texture2D Icon;
public string Name;
public string Message;
public bool Ismic;
public bool Iscam;
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 1e57592024ba7ab4ab283f87f98a49e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,242 @@
using System.Collections.Generic;
using Stary.Evo;
using Unity.RenderStreaming;
using UnityEngine;
using UnityEngine.UI;
namespace Script
{
public interface IRenderStreamingSystem : ISystem
{
void SetUp();
void HangUp();
void SetLocalVideoImage(RawImage localVideoImage);
}
public class RenderStreamingSystem : AbstractSystem, IRenderStreamingSystem
{
[Header("核心组件")] private RenderStreamingSettings settings;
/// <summary>
/// Host连接
/// </summary>
private HostConnection hostConnection;
[Header("Host本地视频")] private VideoStreamSender videoStreamSender;
/// <summary>
/// 麦克风流发送器
/// </summary>
private AudioStreamSender microphoneStreamer;
/// <summary>
/// 每个Participant的UI信息
/// </summary>
private readonly Dictionary<string, ParticipantUI> participantUIs = new();
[Header("Participant视频容器")] private Transform participantVideoContainer;
/// <summary>
/// 渲染流管理
/// </summary>
private SignalingManager _signalingManager;
/// <summary>
/// 本地视频显示
/// </summary>
private RawImage localVideoImage;
protected override void OnInit()
{
var renderStreaming = GameObject.Find("RenderStreaming").transform;
_signalingManager = renderStreaming.GetComponent<SignalingManager>();
hostConnection = renderStreaming.GetComponent<HostConnection>();
videoStreamSender = hostConnection.GetComponent<VideoStreamSender>();
microphoneStreamer = hostConnection.GetComponent<AudioStreamSender>();
if (settings == null)
settings = new RenderStreamingSettings();
if (settings != null)
{
videoStreamSender.width = (uint)settings.StreamSize.x;
videoStreamSender.height = (uint)settings.StreamSize.y;
}
if (_signalingManager.runOnAwake)
return;
if (settings != null)
_signalingManager.useDefaultSettings = settings.UseDefaultSettings;
if (settings?.SignalingSettings != null)
_signalingManager.SetSignalingSettings(settings.SignalingSettings);
if (settings != null)
videoStreamSender.SetCodec(settings.SenderVideoCodec);
_signalingManager.Run();
if (hostConnection != null)
{
hostConnection.OnParticipantConnected += HandleParticipantConnected;
hostConnection.OnParticipantDisconnected += HandleParticipantDisconnected;
}
// 本地视频Sender启动后显示
videoStreamSender.OnStartedStream += OnStartedStream;
}
public override void Dispose()
{
if (hostConnection != null)
{
hostConnection.OnParticipantConnected -= HandleParticipantConnected;
hostConnection.OnParticipantDisconnected -= HandleParticipantDisconnected;
}
videoStreamSender.OnStartedStream -= OnStartedStream;
}
private void OnStartedStream(string id)
{
if (videoStreamSender.sourceWebCamTexture != null && localVideoImage != null)
localVideoImage.texture = videoStreamSender.sourceWebCamTexture;
}
public void SetUp()
{
videoStreamSender.enabled = true;
microphoneStreamer.enabled = true;
hostConnection.RoomConnectionId = this.GetSystem<IGlobalConfigSystem>().GetConnectionId();
hostConnection.CreateConnection(hostConnection.RoomConnectionId);
}
public void HangUp()
{
videoStreamSender.enabled = false;
microphoneStreamer.enabled = false;
hostConnection.DeleteConnection(hostConnection.RoomConnectionId);
// 清理所有Participant UI
foreach (var ui in participantUIs.Values)
if (ui.root != null)
GameObject.Destroy(ui.root);
participantUIs.Clear();
if (localVideoImage != null)
localVideoImage.texture = null;
}
public void SetLocalVideoImage(RawImage localVideoImage)
{
this.localVideoImage = localVideoImage;
}
#region
/// <summary>
/// 新Participant连接成功回调
/// 此时Participant的独立Receiver已创建可以绑定视频显示
/// </summary>
private void HandleParticipantConnected(ParticipantStreams ps)
{
// 创建Participant UI
var ui = CreateParticipantUI(ps.participantId);
participantUIs[ps.participantId] = ui;
// 绑定视频当Receiver收到纹理时更新RawImage
ps.videoReceiver.OnUpdateReceiveTexture += texture =>
{
ui.videoImage.color = Color.white;
// 防止纹理为null时导致RawImage闪黑重协商/track切换时可能短暂为null
if (ui.videoImage != null && texture != null)
ui.videoImage.texture = texture;
};
// 绑定音频AudioSource已在HostConnection中配置
ps.audioReceiver.OnUpdateReceiveAudioSource += source =>
{
if (source != null && !source.isPlaying)
{
source.loop = true;
source.Play();
}
};
Debug.Log($"[MultiParticipantHost] Participant UI created: {ps.participantId}");
}
/// <summary>
/// 动态创建单个Participant的视频显示UI
/// 结构: [NameLabel] + [RawImage(视频画面)]
/// </summary>
private ParticipantUI CreateParticipantUI(string participantId)
{
var ui = new ParticipantUI();
// 根节点
ui.root = new GameObject($"ParticipantUI_{participantId}");
ui.root.transform.SetParent(participantVideoContainer, false);
// 添加VerticalLayoutGroup使内容垂直排列
var vlg = ui.root.AddComponent<VerticalLayoutGroup>();
vlg.childControlWidth = true;
vlg.childControlHeight = false;
vlg.childForceExpandWidth = true;
vlg.childForceExpandHeight = false;
vlg.spacing = 2;
ui.root.transform.GetComponent<RectTransform>().anchorMin = Vector2.zero;
ui.root.transform.GetComponent<RectTransform>().anchorMax = Vector2.one;
// 名称标签
var labelObj = new GameObject("NameLabel");
labelObj.transform.SetParent(ui.root.transform, false);
ui.nameLabel = labelObj.AddComponent<Text>();
ui.nameLabel.text = $"Participant: {participantId}";
ui.nameLabel.fontSize = 14;
ui.nameLabel.color = Color.white;
ui.nameLabel.alignment = TextAnchor.MiddleCenter;
var labelLayout = labelObj.AddComponent<LayoutElement>();
labelLayout.preferredHeight = 20;
// 视频画面
var imageObj = new GameObject("VideoImage");
imageObj.transform.SetParent(ui.root.transform, false);
ui.videoImage = imageObj.AddComponent<RawImage>();
ui.videoImage.color = Color.black;
var imageLayout = imageObj.AddComponent<LayoutElement>();
imageLayout.preferredHeight = 200;
// AspectRatioFitter保持视频比例
var aspectRatio = imageObj.AddComponent<AspectRatioFitter>();
aspectRatio.aspectMode = AspectRatioFitter.AspectMode.FitInParent;
aspectRatio.aspectRatio = 16f / 9f;
return ui;
}
/// <summary>
/// Participant断开连接回调
/// 销毁其UI
/// </summary>
private void HandleParticipantDisconnected(string participantId)
{
if (participantUIs.TryGetValue(participantId, out var ui))
{
if (ui.root != null)
GameObject.Destroy(ui.root);
participantUIs.Remove(participantId);
}
Debug.Log($"[MultiParticipantHost] Participant UI removed: {participantId}");
}
#endregion
}
public class ParticipantUI
{
public Text nameLabel;
public GameObject root;
public RawImage videoImage;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a92be42fdf2e46899bd9e52daf0ad589
timeCreated: 1779630178

View File

@@ -13,11 +13,6 @@ namespace Unity.RenderStreaming
{
public class StartPanel : BasePanel
{
/// <summary>
/// 每个Participant的UI信息
/// </summary>
private readonly Dictionary<string, ParticipantUI> participantUIs = new();
/// <summary>
/// 返回按钮
/// </summary>
@@ -60,30 +55,6 @@ namespace Unity.RenderStreaming
/// </summary>
private Dropdown _timeDropdown;
/// <summary>
/// Host连接
/// </summary>
private HostConnection hostConnection;
/// <summary>
/// 本地视频显示
/// </summary>
private RawImage localVideoImage;
/// <summary>
/// 麦克风流发送器
/// </summary>
private AudioStreamSender microphoneStreamer;
[Header("Participant视频容器")] private Transform participantVideoContainer;
/// <summary>
/// 渲染流管理
/// </summary>
private SignalingManager renderStreaming;
[Header("核心组件")] private RenderStreamingSettings settings;
[Header("Host本地视频")] private VideoStreamSender videoStreamSender;
public override UITweenType TweenType => UITweenType.Fade;
@@ -109,26 +80,6 @@ namespace Unity.RenderStreaming
_profileSpriteIndex = 0;
OnClickProfilePhoto();
renderStreaming = GameObject.FindObjectOfType<SignalingManager>();
hostConnection = GameObject.FindObjectOfType<HostConnection>();
videoStreamSender = hostConnection.GetComponent<VideoStreamSender>();
microphoneStreamer = hostConnection.GetComponent<AudioStreamSender>();
if (settings == null)
settings = new RenderStreamingSettings();
if (settings != null)
{
videoStreamSender.width = (uint)settings.StreamSize.x;
videoStreamSender.height = (uint)settings.StreamSize.y;
}
if (renderStreaming.runOnAwake)
return;
if (settings != null)
renderStreaming.useDefaultSettings = settings.UseDefaultSettings;
if (settings?.SignalingSettings != null)
renderStreaming.SetSignalingSettings(settings.SignalingSettings);
renderStreaming.Run();
}
public override void OnEnter(Action complete = null)
@@ -139,14 +90,6 @@ namespace Unity.RenderStreaming
_randomMeetingId.onClick.AddListener(OnClickRandomMeetingId);
_startButton.onClick.AddListener(OnClickStartButton);
if (hostConnection != null)
{
hostConnection.OnParticipantConnected += HandleParticipantConnected;
hostConnection.OnParticipantDisconnected += HandleParticipantDisconnected;
}
// 本地视频Sender启动后显示
videoStreamSender.OnStartedStream += OnStartedStream;
}
@@ -157,13 +100,6 @@ namespace Unity.RenderStreaming
_profilePhoto.onClick.RemoveListener(OnClickProfilePhoto);
_randomMeetingId.onClick.RemoveListener(OnClickRandomMeetingId);
_startButton.onClick.RemoveListener(OnClickStartButton);
if (hostConnection != null)
{
hostConnection.OnParticipantConnected -= HandleParticipantConnected;
hostConnection.OnParticipantDisconnected -= HandleParticipantDisconnected;
}
videoStreamSender.OnStartedStream -= OnStartedStream;
}
private void OnClickProfilePhoto()
@@ -196,7 +132,7 @@ namespace Unity.RenderStreaming
this.GetSystem<IGlobalConfigSystem>().SetUserId(OnClickRandomUserId());
this.GetSystem<IGlobalConfigSystem>().SetConnectionTimeType(_timeDropdown.value);
this.GetSystem<IGlobalConfigSystem>().SetConnectionId(_meetingId.text);
hostConnection.RoomConnectionId = this.GetSystem<IGlobalConfigSystem>().GetConnectionId();
this.GetSystem<IGlobalConfigSystem>().SetConnectionName(_meetingNameInput.text);
if (!SignalingMessageHelper.IsReady())
{
@@ -216,146 +152,9 @@ namespace Unity.RenderStreaming
}
};
SignalingMessageHelper.SendMessage(JsonConvert.SerializeObject(userInfo));
SetUp();
this.GetSystem<IRenderStreamingSystem>().SetUp();
PanelSystem.PopQueue<StartPanel>();
PanelSystem.PushQueue<MainPanel>();
}
private void OnStartedStream(string id)
{
if (videoStreamSender.sourceWebCamTexture != null)
localVideoImage.texture = videoStreamSender.sourceWebCamTexture;
}
private class ParticipantUI
{
public Text nameLabel;
public GameObject root;
public RawImage videoImage;
}
#region
private void SetUp()
{
videoStreamSender.enabled = true;
if (settings != null)
videoStreamSender.SetCodec(settings.SenderVideoCodec);
hostConnection.CreateConnection(hostConnection.RoomConnectionId);
}
private void HangUp()
{
hostConnection.DeleteConnection(hostConnection.RoomConnectionId);
// 清理所有Participant UI
foreach (var ui in participantUIs.Values)
if (ui.root != null)
GameObject.Destroy(ui.root);
participantUIs.Clear();
localVideoImage.texture = null;
}
/// <summary>
/// 新Participant连接成功回调
/// 此时Participant的独立Receiver已创建可以绑定视频显示
/// </summary>
private void HandleParticipantConnected(ParticipantStreams ps)
{
// 创建Participant UI
var ui = CreateParticipantUI(ps.participantId);
participantUIs[ps.participantId] = ui;
// 绑定视频当Receiver收到纹理时更新RawImage
ps.videoReceiver.OnUpdateReceiveTexture += texture =>
{
ui.videoImage.color = Color.white;
// 防止纹理为null时导致RawImage闪黑重协商/track切换时可能短暂为null
if (ui.videoImage != null && texture != null)
ui.videoImage.texture = texture;
};
// 绑定音频AudioSource已在HostConnection中配置
ps.audioReceiver.OnUpdateReceiveAudioSource += source =>
{
if (source != null && !source.isPlaying)
{
source.loop = true;
source.Play();
}
};
Debug.Log($"[MultiParticipantHost] Participant UI created: {ps.participantId}");
}
/// <summary>
/// 动态创建单个Participant的视频显示UI
/// 结构: [NameLabel] + [RawImage(视频画面)]
/// </summary>
private ParticipantUI CreateParticipantUI(string participantId)
{
var ui = new ParticipantUI();
// 根节点
ui.root = new GameObject($"ParticipantUI_{participantId}");
ui.root.transform.SetParent(participantVideoContainer, false);
// 添加VerticalLayoutGroup使内容垂直排列
var vlg = ui.root.AddComponent<VerticalLayoutGroup>();
vlg.childControlWidth = true;
vlg.childControlHeight = false;
vlg.childForceExpandWidth = true;
vlg.childForceExpandHeight = false;
vlg.spacing = 2;
ui.root.transform.GetComponent<RectTransform>().anchorMin = Vector2.zero;
ui.root.transform.GetComponent<RectTransform>().anchorMax = Vector2.one;
// 名称标签
var labelObj = new GameObject("NameLabel");
labelObj.transform.SetParent(ui.root.transform, false);
ui.nameLabel = labelObj.AddComponent<Text>();
ui.nameLabel.text = $"Participant: {participantId}";
ui.nameLabel.fontSize = 14;
ui.nameLabel.color = Color.white;
ui.nameLabel.alignment = TextAnchor.MiddleCenter;
var labelLayout = labelObj.AddComponent<LayoutElement>();
labelLayout.preferredHeight = 20;
// 视频画面
var imageObj = new GameObject("VideoImage");
imageObj.transform.SetParent(ui.root.transform, false);
ui.videoImage = imageObj.AddComponent<RawImage>();
ui.videoImage.color = Color.black;
var imageLayout = imageObj.AddComponent<LayoutElement>();
imageLayout.preferredHeight = 200;
// AspectRatioFitter保持视频比例
var aspectRatio = imageObj.AddComponent<AspectRatioFitter>();
aspectRatio.aspectMode = AspectRatioFitter.AspectMode.FitInParent;
aspectRatio.aspectRatio = 16f / 9f;
return ui;
}
/// <summary>
/// Participant断开连接回调
/// 销毁其UI
/// </summary>
private void HandleParticipantDisconnected(string participantId)
{
if (participantUIs.TryGetValue(participantId, out var ui))
{
if (ui.root != null)
GameObject.Destroy(ui.root);
participantUIs.Remove(participantId);
}
Debug.Log($"[MultiParticipantHost] Participant UI removed: {participantId}");
}
#endregion
}
}