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

@@ -807,7 +807,7 @@ GameObject:
- component: {fileID: 5633745582843563864} - component: {fileID: 5633745582843563864}
- component: {fileID: 2745806976415084587} - component: {fileID: 2745806976415084587}
m_Layer: 5 m_Layer: 5
m_Name: 'menuBar ' m_Name: menuBar
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
@@ -5468,57 +5468,6 @@ MonoBehaviour:
m_FillOrigin: 0 m_FillOrigin: 0
m_UseSpriteMesh: 0 m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1 m_PixelsPerUnitMultiplier: 1
--- !u!1 &6378174656737304708
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 564683241228581302}
- component: {fileID: 4786814708591545236}
m_Layer: 5
m_Name: Pool
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &564683241228581302
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6378174656737304708}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 6018946838727076121}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &4786814708591545236
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6378174656737304708}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1970feb7fdb702645b753cd8ed1be4ea, type: 3}
m_Name:
m_EditorClassIdentifier:
prefab: {fileID: 8495991377958309690, guid: ac2316fabaf14dc409bee0e9c34cae94, type: 3}
preload: 20
parent: {fileID: 5467042597978700984}
--- !u!1 &6412793980989755428 --- !u!1 &6412793980989755428
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -5965,7 +5914,6 @@ RectTransform:
m_Children: m_Children:
- {fileID: 3211075651667160076} - {fileID: 3211075651667160076}
- {fileID: 8993963496967448533} - {fileID: 8993963496967448533}
- {fileID: 564683241228581302}
m_Father: {fileID: 6596158407599272745} m_Father: {fileID: 6596158407599272745}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMin: {x: 0.5, y: 0.5}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
using RenderStreaming; using RenderStreaming;
using Script.Util; using Script.Util;
using Stary.Evo; using Stary.Evo;
using Stary.Evo.UIFarme;
using Unity.RenderStreaming; using Unity.RenderStreaming;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
namespace Script namespace Script
{ {
public class MeetingInfoList : IController public class MeetingInfoListController : IController
{ {
private MainPanel _mainPanel; private MainPanel _mainPanel;
public GameObject PanelGo; public GameObject PanelGo;
@@ -29,6 +31,15 @@ namespace Script
private GameObjectPool _objectPool; 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) public void Initialize(GameObject panelGo, MainPanel mainPanel)
{ {
PanelGo = panelGo; PanelGo = panelGo;
@@ -37,17 +48,87 @@ namespace Script
_content = panelGo.transform.Find("MeetingGrid/Viewport/Content"); _content = panelGo.transform.Find("MeetingGrid/Viewport/Content");
_meetingNum = panelGo.transform.Find("MeetingNum/Num").GetComponent<Text>(); _meetingNum = panelGo.transform.Find("MeetingNum/Num").GetComponent<Text>();
_objectPool = PanelGo.transform.Find("MeetingGrid/Pool").GetComponent<GameObjectPool>(); _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() public void OnEnter()
{ {
LoadUsers(); 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>().OnMediaStateChangeReceived += OnMediaStateChangeReceived;
GameObject.FindObjectOfType<MessageChannel>().OnUserInfoMessageReceived += OnUserInfoMessageReceived; GameObject.FindObjectOfType<MessageChannel>().OnUserInfoMessageReceived += OnUserInfoMessageReceived;
GameObject.FindObjectOfType<HostConnection>().OnParticipantDisconnected += OnParticipantDisconnected; GameObject.FindObjectOfType<HostConnection>().OnParticipantDisconnected += OnParticipantDisconnected;
} }
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>
/// 当有用户断开连接时调用 /// 当有用户断开连接时调用
/// </summary> /// </summary>
@@ -225,6 +306,9 @@ namespace Script
GameObject.FindObjectOfType<MessageChannel>().OnMediaStateChangeReceived -= OnMediaStateChangeReceived; GameObject.FindObjectOfType<MessageChannel>().OnMediaStateChangeReceived -= OnMediaStateChangeReceived;
GameObject.FindObjectOfType<MessageChannel>().OnUserInfoMessageReceived -= OnUserInfoMessageReceived; GameObject.FindObjectOfType<MessageChannel>().OnUserInfoMessageReceived -= OnUserInfoMessageReceived;
GameObject.FindObjectOfType<HostConnection>().OnParticipantDisconnected -= OnParticipantDisconnected; GameObject.FindObjectOfType<HostConnection>().OnParticipantDisconnected -= OnParticipantDisconnected;
foreach (var info in _meetingList.Values) _objectPool.Release(info.participant);
_meetingList.Clear();
} }
public void Destroy() 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 public class StartPanel : BasePanel
{ {
/// <summary>
/// 每个Participant的UI信息
/// </summary>
private readonly Dictionary<string, ParticipantUI> participantUIs = new();
/// <summary> /// <summary>
/// 返回按钮 /// 返回按钮
/// </summary> /// </summary>
@@ -60,30 +55,6 @@ namespace Unity.RenderStreaming
/// </summary> /// </summary>
private Dropdown _timeDropdown; 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; public override UITweenType TweenType => UITweenType.Fade;
@@ -109,26 +80,6 @@ namespace Unity.RenderStreaming
_profileSpriteIndex = 0; _profileSpriteIndex = 0;
OnClickProfilePhoto(); 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) public override void OnEnter(Action complete = null)
@@ -139,14 +90,6 @@ namespace Unity.RenderStreaming
_randomMeetingId.onClick.AddListener(OnClickRandomMeetingId); _randomMeetingId.onClick.AddListener(OnClickRandomMeetingId);
_startButton.onClick.AddListener(OnClickStartButton); _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); _profilePhoto.onClick.RemoveListener(OnClickProfilePhoto);
_randomMeetingId.onClick.RemoveListener(OnClickRandomMeetingId); _randomMeetingId.onClick.RemoveListener(OnClickRandomMeetingId);
_startButton.onClick.RemoveListener(OnClickStartButton); _startButton.onClick.RemoveListener(OnClickStartButton);
if (hostConnection != null)
{
hostConnection.OnParticipantConnected -= HandleParticipantConnected;
hostConnection.OnParticipantDisconnected -= HandleParticipantDisconnected;
}
videoStreamSender.OnStartedStream -= OnStartedStream;
} }
private void OnClickProfilePhoto() private void OnClickProfilePhoto()
@@ -196,7 +132,7 @@ namespace Unity.RenderStreaming
this.GetSystem<IGlobalConfigSystem>().SetUserId(OnClickRandomUserId()); this.GetSystem<IGlobalConfigSystem>().SetUserId(OnClickRandomUserId());
this.GetSystem<IGlobalConfigSystem>().SetConnectionTimeType(_timeDropdown.value); this.GetSystem<IGlobalConfigSystem>().SetConnectionTimeType(_timeDropdown.value);
this.GetSystem<IGlobalConfigSystem>().SetConnectionId(_meetingId.text); this.GetSystem<IGlobalConfigSystem>().SetConnectionId(_meetingId.text);
hostConnection.RoomConnectionId = this.GetSystem<IGlobalConfigSystem>().GetConnectionId();
this.GetSystem<IGlobalConfigSystem>().SetConnectionName(_meetingNameInput.text); this.GetSystem<IGlobalConfigSystem>().SetConnectionName(_meetingNameInput.text);
if (!SignalingMessageHelper.IsReady()) if (!SignalingMessageHelper.IsReady())
{ {
@@ -216,146 +152,9 @@ namespace Unity.RenderStreaming
} }
}; };
SignalingMessageHelper.SendMessage(JsonConvert.SerializeObject(userInfo)); SignalingMessageHelper.SendMessage(JsonConvert.SerializeObject(userInfo));
SetUp(); this.GetSystem<IRenderStreamingSystem>().SetUp();
PanelSystem.PopQueue<StartPanel>(); PanelSystem.PopQueue<StartPanel>();
PanelSystem.PushQueue<MainPanel>(); 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
} }
} }

View File

@@ -214,7 +214,9 @@ namespace Unity.RenderStreaming.Signaling
/// </summary> /// </summary>
private bool IsParticipantConnectionId(string connectionId) private bool IsParticipantConnectionId(string connectionId)
{ {
if (!string.IsNullOrEmpty(connectionId) && m_participantToRoom != null && m_participantToRoom.Count > 0)
return m_participantToRoom.ContainsKey(connectionId); return m_participantToRoom.ContainsKey(connectionId);
return false;
} }
/// <summary> /// <summary>