start开发完成

This commit is contained in:
2026-05-17 11:35:43 +08:00
parent d2235bf7b7
commit ce9b1e85e9
67 changed files with 4285 additions and 149 deletions

View 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
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 36903d0e77054ebfb5c85727f54eee63
timeCreated: 1778830795

View File

@@ -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
View 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;
}
}

View File

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

View File

@@ -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());
}

View 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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 653ec51da16e4dd786d0d20f29661d54
timeCreated: 1778848877

View File

@@ -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
View 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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6ff7eba1c99e4cfb9cdaa1fa16bb9e15
timeCreated: 1778820120