start开发完成
This commit is contained in:
70
Assets/Script/ AndroidImagePicker.cs
Normal file
70
Assets/Script/ AndroidImagePicker.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace Unity.RenderStreaming
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
public class AndroidImagePicker : MonoBehaviour
|
||||
{
|
||||
private static AndroidImagePicker _instance;
|
||||
private Action<Texture2D> _callback;
|
||||
private AndroidJavaObject _activity;
|
||||
private bool _waitingForResult;
|
||||
private string _pickedImagePath;
|
||||
|
||||
public static AndroidImagePicker Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
var go = new GameObject(nameof(AndroidImagePicker));
|
||||
DontDestroyOnLoad(go);
|
||||
_instance = go.AddComponent<AndroidImagePicker>();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void PickImage(Action<Texture2D> onImagePicked)
|
||||
{
|
||||
_callback = onImagePicked;
|
||||
|
||||
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
{
|
||||
_activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
}
|
||||
|
||||
_activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
|
||||
{
|
||||
using (AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent",
|
||||
"android.intent.action.PICK"))
|
||||
{
|
||||
intent.Call<AndroidJavaObject>("setType", "image/*");
|
||||
using (AndroidJavaObject i = intent.Call<AndroidJavaObject>("createChooser",
|
||||
_activity, "选择头像"))
|
||||
{
|
||||
_activity.Call("startActivity", i);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
_waitingForResult = true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_waitingForResult) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (_activity == null) return;
|
||||
using (AndroidJavaClass uriClass = new AndroidJavaClass("android.net.Uri"))
|
||||
{
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
3
Assets/Script/ AndroidImagePicker.cs.meta
Normal file
3
Assets/Script/ AndroidImagePicker.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36903d0e77054ebfb5c85727f54eee63
|
||||
timeCreated: 1778830795
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Stary.Evo;
|
||||
@@ -9,26 +7,50 @@ public interface IGlobalConfigSystem : ISystem
|
||||
{
|
||||
public string GetConnectionId();
|
||||
public void SetConnectionId(string connectionId);
|
||||
|
||||
|
||||
public string GetConnectionName();
|
||||
public void SetConnectionName(string connectionName);
|
||||
|
||||
|
||||
public UniTaskVoid StartConnectionTime();
|
||||
public string GetConnectionTime();
|
||||
|
||||
|
||||
public void StopConnectionTime();
|
||||
|
||||
|
||||
public int GetConnectionTimeType();
|
||||
public void SetConnectionTimeType(int connectionTimeType);
|
||||
|
||||
public Texture2D GetConnectionTexture();
|
||||
public void SetConnectionTexture(Texture2D connectionTexture);
|
||||
}
|
||||
|
||||
public class GlobalConfigSystem : AbstractSystem, IGlobalConfigSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接ID
|
||||
/// </summary>
|
||||
private string _connectionId;
|
||||
|
||||
/// <summary>
|
||||
/// 连接名称
|
||||
/// </summary>
|
||||
private string _connectionName;
|
||||
|
||||
/// <summary>
|
||||
/// 连接图标
|
||||
/// </summary>
|
||||
private Texture2D _connectionTexture;
|
||||
|
||||
/// <summary>
|
||||
/// 连接时间
|
||||
/// </summary>
|
||||
private float _connectionTime;
|
||||
|
||||
/// <summary>
|
||||
/// 连接时间类型
|
||||
/// </summary>
|
||||
private int _connectionTimeType;
|
||||
|
||||
private CancellationTokenSource _cts;
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public override void Dispose()
|
||||
@@ -74,17 +96,17 @@ public class GlobalConfigSystem : AbstractSystem, IGlobalConfigSystem
|
||||
_connectionTime = 0;
|
||||
while (!_cts.IsCancellationRequested)
|
||||
{
|
||||
_connectionTime+=Time.deltaTime;
|
||||
_connectionTime += Time.deltaTime;
|
||||
await UniTask.Yield(_cts.Token); // 等一帧,等价于 Update
|
||||
}
|
||||
}
|
||||
|
||||
public string GetConnectionTime()
|
||||
{
|
||||
int totalSeconds = (int)_connectionTime;
|
||||
int hours = totalSeconds / 3600;
|
||||
int minutes = totalSeconds % 3600 / 60;
|
||||
int seconds = totalSeconds % 60;
|
||||
var totalSeconds = (int)_connectionTime;
|
||||
var hours = totalSeconds / 3600;
|
||||
var minutes = totalSeconds % 3600 / 60;
|
||||
var seconds = totalSeconds % 60;
|
||||
return $"{hours:D2}:{minutes:D2}:{seconds:D2}";
|
||||
}
|
||||
|
||||
@@ -94,4 +116,34 @@ public class GlobalConfigSystem : AbstractSystem, IGlobalConfigSystem
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
public int GetConnectionTimeType()
|
||||
{
|
||||
return _connectionTimeType;
|
||||
}
|
||||
|
||||
public void SetConnectionTimeType(int connectionTimeType)
|
||||
{
|
||||
_connectionTimeType = connectionTimeType;
|
||||
}
|
||||
|
||||
public Texture2D GetConnectionTexture()
|
||||
{
|
||||
if (_connectionTexture == null)
|
||||
{
|
||||
Debug.LogWarning("GlobalConfigSystem: GetConnectionTexture not set");
|
||||
return null;
|
||||
}
|
||||
|
||||
return _connectionTexture;
|
||||
}
|
||||
|
||||
public void SetConnectionTexture(Texture2D connectionTexture)
|
||||
{
|
||||
_connectionTexture = connectionTexture;
|
||||
}
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
19
Assets/Script/Main.cs
Normal file
19
Assets/Script/Main.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using RenderStreaming;
|
||||
using Stary.Evo;
|
||||
using Stary.Evo.UIFarme;
|
||||
using Unity.RenderStreaming;
|
||||
using UnityEngine;
|
||||
|
||||
public class Main : MonoBehaviour, IController
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
this.GetSystem<IPanelSystem>().PushQueue<StartPanel>();
|
||||
}
|
||||
|
||||
|
||||
public IArchitecture GetArchitecture()
|
||||
{
|
||||
return MainArchitecture.Interface;
|
||||
}
|
||||
}
|
||||
11
Assets/Script/Main.cs.meta
Normal file
11
Assets/Script/Main.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e7ee59a6ab9e234bb2d7cec8eaf1563
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,5 @@
|
||||
using Stary.Evo;
|
||||
using Stary.Evo.UIFarme;
|
||||
|
||||
namespace RenderStreaming
|
||||
{
|
||||
@@ -8,6 +9,7 @@ namespace RenderStreaming
|
||||
{
|
||||
//注册示例
|
||||
//RegisterSystem<IScoreSystem>(new ScoreSystem());
|
||||
RegisterSystem<IPanelSystem>(new PanelSystem(new ResourcesAssetLoader()));
|
||||
RegisterSystem<IGlobalConfigSystem>(new GlobalConfigSystem());
|
||||
RegisterSystem<ITimerSystem>(new TimerSystem());
|
||||
}
|
||||
|
||||
72
Assets/Script/MainPanel.cs
Normal file
72
Assets/Script/MainPanel.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Stary.Evo.UIFarme;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Script
|
||||
{
|
||||
public class MainPanel : BasePanel
|
||||
{
|
||||
/// <summary>
|
||||
/// 退出按钮
|
||||
/// </summary>
|
||||
private Button _arrowLeft;
|
||||
|
||||
/// <summary>
|
||||
/// 房间ID
|
||||
/// </summary>
|
||||
private Text _idText;
|
||||
|
||||
/// <summary>
|
||||
/// 房间人数
|
||||
/// </summary>
|
||||
private Text _roomPeopleNumText;
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
private Text _timeText;
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
private Text _titleText;
|
||||
|
||||
public override UITweenType TweenType => UITweenType.Fade;
|
||||
|
||||
public override string UIPath => "Canvas";
|
||||
|
||||
|
||||
public override void Initialize(GameObject panelGo)
|
||||
{
|
||||
base.Initialize(panelGo);
|
||||
_titleText = panelGo.transform.Find("Header/title").GetComponent<Text>();
|
||||
_timeText = panelGo.transform.Find("Header/time").GetComponent<Text>();
|
||||
_arrowLeft = panelGo.transform.Find("Header/arrow-left").GetComponent<Button>();
|
||||
_idText = panelGo.transform.Find("MeetingInfoCard/meeting/id").GetComponent<Text>();
|
||||
_roomPeopleNumText = panelGo.transform.Find("MeetingInfoCard/numberOfPeople/number").GetComponent<Text>();
|
||||
}
|
||||
|
||||
public override void OnEnter(Action complete = null)
|
||||
{
|
||||
base.OnEnter(complete);
|
||||
_arrowLeft.onClick.AddListener(OnArrowLeftClick);
|
||||
}
|
||||
|
||||
|
||||
public override void OnExit(float delay = 0)
|
||||
{
|
||||
base.OnExit(delay);
|
||||
_arrowLeft.onClick.RemoveListener(OnArrowLeftClick);
|
||||
}
|
||||
|
||||
private void OnArrowLeftClick()
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Script/MainPanel.cs.meta
Normal file
3
Assets/Script/MainPanel.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 653ec51da16e4dd786d0d20f29661d54
|
||||
timeCreated: 1778848877
|
||||
@@ -1,26 +1,11 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using RenderStreaming;
|
||||
using Stary.Evo;
|
||||
using UnityEngine;
|
||||
|
||||
public class MeetingChatController : MonoBehaviour,IController
|
||||
public class MeetingChatController : MonoBehaviour, IController
|
||||
{
|
||||
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public IArchitecture GetArchitecture()
|
||||
{
|
||||
return MainArchitecture.Interface;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
335
Assets/Script/StartPanel.cs
Normal file
335
Assets/Script/StartPanel.cs
Normal file
@@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Script;
|
||||
using Stary.Evo;
|
||||
using Stary.Evo.UIFarme;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEngine.UI;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Unity.RenderStreaming
|
||||
{
|
||||
public class StartPanel : BasePanel
|
||||
{
|
||||
/// <summary>
|
||||
/// 每个Participant的UI信息
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, ParticipantUI> participantUIs = new();
|
||||
|
||||
/// <summary>
|
||||
/// 返回按钮
|
||||
/// </summary>
|
||||
private Button _arrowLeft;
|
||||
|
||||
private Text _meetingId;
|
||||
|
||||
/// <summary>
|
||||
/// 房间号输入框
|
||||
/// </summary>
|
||||
private InputField _meetingNameInput;
|
||||
|
||||
private Image _profileImage;
|
||||
|
||||
/// <summary>
|
||||
/// 头像按钮
|
||||
/// </summary>
|
||||
private Button _profilePhoto;
|
||||
|
||||
private int _profileSpriteIndex;
|
||||
private Sprite[] _profileSprites;
|
||||
|
||||
/// <summary>
|
||||
/// 随机房间号按钮
|
||||
/// </summary>
|
||||
private Button _randomMeetingId;
|
||||
|
||||
/// <summary>
|
||||
/// 头像Sprite Atlas
|
||||
/// </summary>
|
||||
private SpriteAtlas _spriteAtlas;
|
||||
|
||||
/// <summary>
|
||||
/// 开始按钮
|
||||
/// </summary>
|
||||
private Button _startButton;
|
||||
|
||||
/// <summary>
|
||||
/// 时间下拉选择框
|
||||
/// </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;
|
||||
|
||||
public override string UIPath => "Canvas";
|
||||
|
||||
public override void Initialize(GameObject panelGo)
|
||||
{
|
||||
base.Initialize(panelGo);
|
||||
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();
|
||||
_arrowLeft = panelGo.transform.Find("Herder/arrow-left").GetComponent<Button>();
|
||||
_profilePhoto = panelGo.transform.Find("HeadPortraits/MeetingInfoCard").GetComponent<Button>();
|
||||
_profileImage = _profilePhoto.transform.Find("mask/sprite").GetComponent<Image>();
|
||||
_meetingNameInput = panelGo.transform.Find("card/search/InputField").GetComponent<InputField>();
|
||||
_meetingId = panelGo.transform.Find("card/huiyiID/connectionId").GetComponent<Text>();
|
||||
_randomMeetingId = panelGo.transform.Find("card/huiyiID/Button").GetComponent<Button>();
|
||||
_timeDropdown = panelGo.transform.Find("card/time/Dropdown").GetComponent<Dropdown>();
|
||||
_startButton = panelGo.transform.Find("invite").GetComponent<Button>();
|
||||
_spriteAtlas = Resources.Load<SpriteAtlas>("SpriteAtlas");
|
||||
if (_spriteAtlas != null)
|
||||
{
|
||||
_profileSprites = new Sprite[_spriteAtlas.spriteCount];
|
||||
_spriteAtlas.GetSprites(_profileSprites);
|
||||
}
|
||||
|
||||
_profileSpriteIndex = 0;
|
||||
OnClickProfilePhoto();
|
||||
}
|
||||
|
||||
public override void OnEnter(Action complete = null)
|
||||
{
|
||||
base.OnEnter(complete);
|
||||
_arrowLeft.onClick.AddListener(OnClickArrowLeft);
|
||||
_profilePhoto.onClick.AddListener(OnClickProfilePhoto);
|
||||
_randomMeetingId.onClick.AddListener(OnClickRandomMeetingId);
|
||||
|
||||
_startButton.onClick.AddListener(OnClickStartButton);
|
||||
if (hostConnection != null)
|
||||
{
|
||||
hostConnection.OnParticipantConnected += HandleParticipantConnected;
|
||||
hostConnection.OnParticipantDisconnected += HandleParticipantDisconnected;
|
||||
}
|
||||
|
||||
// 本地视频:Sender启动后显示
|
||||
videoStreamSender.OnStartedStream += OnStartedStream;
|
||||
}
|
||||
|
||||
|
||||
public override void OnExit(float delay = 0)
|
||||
{
|
||||
base.OnExit(delay);
|
||||
_arrowLeft.onClick.RemoveListener(OnClickArrowLeft);
|
||||
_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()
|
||||
{
|
||||
if (_profileSprites == null || _profileSprites.Length == 0) return;
|
||||
_profileImage.sprite = _profileSprites[_profileSpriteIndex];
|
||||
this.GetSystem<IGlobalConfigSystem>().SetConnectionTexture(_profileSprites[_profileSpriteIndex].texture);
|
||||
_profileSpriteIndex = (_profileSpriteIndex + 1) % _profileSprites.Length;
|
||||
}
|
||||
|
||||
private void OnClickArrowLeft()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnClickRandomMeetingId()
|
||||
{
|
||||
var meetingId = $"{Random.Range(100, 999)}-{Random.Range(100, 999)}-{Random.Range(100, 999)}";
|
||||
_meetingId.text = meetingId;
|
||||
if (string.IsNullOrEmpty(_meetingNameInput.text)) _meetingNameInput.text = $"{meetingId}的房间";
|
||||
}
|
||||
|
||||
private void OnClickStartButton()
|
||||
{
|
||||
this.GetSystem<IGlobalConfigSystem>().SetConnectionTimeType(_timeDropdown.value);
|
||||
this.GetSystem<IGlobalConfigSystem>().SetConnectionId(_meetingId.text);
|
||||
hostConnection.RoomConnectionId = this.GetSystem<IGlobalConfigSystem>().GetConnectionId();
|
||||
this.GetSystem<IGlobalConfigSystem>().SetConnectionName(_meetingNameInput.text);
|
||||
this.GetSystem<IGlobalConfigSystem>().SetConnectionTexture(Texture2D.whiteTexture);
|
||||
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
|
||||
}
|
||||
}
|
||||
3
Assets/Script/StartPanel.cs.meta
Normal file
3
Assets/Script/StartPanel.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ff7eba1c99e4cfb9cdaa1fa16bb9e15
|
||||
timeCreated: 1778820120
|
||||
Reference in New Issue
Block a user