Files
webRtc/Assets/Script/RenderStreamingSystem.cs
2026-06-03 22:05:03 +08:00

257 lines
9.2 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using Stary.Evo;
using Unity.RenderStreaming;
using Unity.XR.XREAL;
using UnityEngine;
using UnityEngine.UI;
namespace Script
{
public interface IRenderStreamingSystem : ISystem
{
void SetUp();
void HangUp();
RenderTexture GetRenderStreamingTexture();
}
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;
private YUVToRenderTexture _yuvToRenderTexture;
protected override void OnInit()
{
participantVideoContainer = GameObject.Find("CanvasMain/ParticipantUI").transform;
var renderStreaming = GameObject.Find("RenderStreaming").transform;
_signalingManager = renderStreaming.GetComponent<SignalingManager>();
hostConnection = renderStreaming.GetComponent<HostConnection>();
videoStreamSender = hostConnection.GetComponent<VideoStreamSender>();
microphoneStreamer = hostConnection.GetComponent<AudioStreamSender>();
_yuvToRenderTexture = renderStreaming.AddOrGetComponent<YUVToRenderTexture>();
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);
if (_yuvToRenderTexture != null)
{
_yuvToRenderTexture.Play();
videoStreamSender.sourceTexture = _yuvToRenderTexture.localRenderTexture;
}
}
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 (_yuvToRenderTexture != null)
_yuvToRenderTexture.Stop();
}
#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}").AddComponent<RectTransform>().gameObject;
ui.root.transform.SetParent(participantVideoContainer, false);
ui.root.transform.GetComponent<RectTransform>().anchorMin = new Vector2(0,1);
ui.root.transform.GetComponent<RectTransform>().anchorMax = new Vector2(0,1);
ui.root.transform.GetComponent<RectTransform>().pivot = new Vector2(0.5f,0.5f);
if (participantUIs.Count % 2 == 0)
{
ui.root.transform.rotation = Quaternion.Euler(0, -45, 0);
}
else
{
ui.root.transform.rotation = Quaternion.Euler(0, 45, 0);
}
// 名称标签
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}");
}
public RenderTexture GetRenderStreamingTexture()
{
if (_yuvToRenderTexture.localRenderTexture == null)
{
Debug.LogError("RenderTexture 未初始化");
return null;
}
return _yuvToRenderTexture.localRenderTexture;
}
#endregion
}
public class ParticipantUI
{
public Text nameLabel;
public GameObject root;
public RawImage videoImage;
}
}