using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using Unity.RenderStreaming.Signaling; using Unity.WebRTC; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace Unity.RenderStreaming { /// /// Manages the signaling process for Unity RenderStreaming. /// /// /// /// [AddComponentMenu("Render Streaming/Signaling Manager")] public sealed class SignalingManager : MonoBehaviour { internal const string UseDefaultPropertyName = nameof(m_useDefault); internal const string SignalingSettingsObjectPropertyName = nameof(signalingSettingsObject); internal const string SignalingSettingsPropertyName = nameof(signalingSettings); internal const string HandlersPropertyName = nameof(handlers); internal const string RunOnAwakePropertyName = nameof(runOnAwake); internal const string EvaluateCommandlineArgumentsPropertyName = nameof(evaluateCommandlineArguments); #pragma warning disable 0649 [SerializeField, Tooltip("Use settings in Project Settings Window.")] private bool m_useDefault = true; [SerializeField] internal SignalingSettingsObject signalingSettingsObject; [SerializeReference, SignalingSettings] private SignalingSettings signalingSettings = new WebSocketSignalingSettings(); [SerializeField, Tooltip("List of handlers of signaling process.")] private List handlers = new List(); /// /// Indicates whether the signaling process should automatically start when the Awake method is called. /// [SerializeField, Tooltip("Automatically started when called Awake method.")] public bool runOnAwake = true; /// /// Indicates whether to evaluate command line arguments if launching runtime on the command line. /// [SerializeField, Tooltip("Evaluate commandline arguments if launching runtime on the command line.")] public bool evaluateCommandlineArguments = true; #pragma warning restore 0649 private SignalingManagerInternal m_instance; private SignalingEventProvider m_provider; private bool m_running; /// /// Gets a value indicating whether the signaling process is running. /// public bool Running => m_running; static ISignaling CreateSignaling(SignalingSettings settings, SynchronizationContext context) { if (settings.signalingClass == null) { throw new ArgumentException($"Signaling type is undefined. {settings.signalingClass}"); } object[] args = { settings, context }; return (ISignaling)Activator.CreateInstance(settings.signalingClass, args); } /// /// Use settings in Project Settings. /// public bool useDefaultSettings { get { return m_useDefault; } set { m_useDefault = value; } } /// /// Sets the signaling settings. /// /// /// /// var settings = new WebSocketSignalingSettings("ws://example.com", new[] /// { /// new IceServer (urls: new[] {"stun:stun.l.google.com:19302"}) /// }); /// signalingManager.SetSignalingSettings(settings); /// /// /// The signaling settings. /// Thrown when the signaling process has already started. /// Thrown when the settings are null. public void SetSignalingSettings(SignalingSettings settings) { if (m_running) throw new InvalidOperationException("The Signaling process has already started."); if (settings == null) throw new ArgumentNullException("settings"); signalingSettings = settings; } /// /// Gets the signaling settings. /// /// /// /// var settings = signalingManager.GetSignalingSettings(); /// if (settings is WebSocketSignalingSettings webSocketSettings) /// { /// Debug.Log($"WebSocket URL: {webSocketSettings.url}"); /// } /// /// /// The signaling settings. public SignalingSettings GetSignalingSettings() { return signalingSettings; } /// /// Adds a signaling handler. /// /// /// /// var handler = instance.GetComponent(); /// signalingManager.AddSignalingHandler(handler); /// /// /// The signaling handler to add. public void AddSignalingHandler(SignalingHandlerBase handlerBase) { if (handlers.Contains(handlerBase)) { return; } handlers.Add(handlerBase); if (!m_running) { return; } handlerBase.SetHandler(m_instance); m_provider.Subscribe(handlerBase); } /// /// Removes a signaling handler. /// /// /// /// var handler = instance.GetComponent(); /// signalingManager.RemoveSignalingHandler(handler); /// /// /// The signaling handler to remove. public void RemoveSignalingHandler(SignalingHandlerBase handlerBase) { handlers.Remove(handlerBase); if (!m_running) { return; } handlerBase.SetHandler(null); m_provider.Unsubscribe(handlerBase); } /// /// Runs the signaling process. /// /// The signaling instance to use. If null, a new instance will be created. /// The signaling handlers to use. If null, the existing handlers will be used. /// /// /// signalingManager.Run(); /// /// public void Run( ISignaling signaling = null, SignalingHandlerBase[] handlers = null) { _Run(null, signaling, handlers); } /// /// Runs the signaling process with the specified RTC configuration. /// /// /// /// var rtcConfig = new RTCConfiguration /// { /// iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } } /// }; /// signalingManager.Run(rtcConfig); /// /// /// The RTC configuration. /// The signaling instance to use. If null, a new instance will be created. /// The signaling handlers to use. If null, the existing handlers will be used. /// To use this method, the WebRTC package is required. public void Run( RTCConfiguration conf, ISignaling signaling = null, SignalingHandlerBase[] handlers = null ) { _Run(conf, signaling, handlers); } #if UNITY_EDITOR bool IsValidSignalingSettingsObject(SignalingSettingsObject asset) { if (asset == null) return false; if (AssetDatabase.GetAssetPath(asset).IndexOf("Assets", StringComparison.Ordinal) != 0) return false; return true; } #endif private void _Run( RTCConfiguration? conf = null, ISignaling signaling = null, SignalingHandlerBase[] handlers = null ) { var settings = m_useDefault ? RenderStreaming.GetSignalingSettings() : signalingSettings; #if !UNITY_EDITOR var arguments = Environment.GetCommandLineArgs(); if (evaluateCommandlineArguments && arguments.Length > 1) { if (!EvaluateCommandlineArguments(ref settings, arguments)) { RenderStreaming.Logger.Log(LogType.Error, "Command line arguments are invalid."); } } #endif int i = 0; RTCIceServer[] iceServers = new RTCIceServer[settings.iceServers.Count()]; foreach (var iceServer in settings.iceServers) { iceServers[i] = (RTCIceServer)iceServer; i++; } RTCConfiguration _conf = conf.GetValueOrDefault(new RTCConfiguration { iceServers = iceServers }); ISignaling _signaling = signaling ?? CreateSignaling(settings, SynchronizationContext.Current); RenderStreamingDependencies dependencies = new RenderStreamingDependencies { config = _conf, signaling = _signaling, startCoroutine = StartCoroutine, stopCoroutine = StopCoroutine, resentOfferInterval = 5.0f, }; var _handlers = (handlers ?? this.handlers.AsEnumerable()).Where(_ => _ != null); if (_handlers.Count() == 0) throw new InvalidOperationException("Handler list is empty."); m_instance = new SignalingManagerInternal(ref dependencies); m_provider = new SignalingEventProvider(m_instance); foreach (var handler in _handlers) { handler.SetHandler(m_instance); m_provider.Subscribe(handler); } m_running = true; } internal static bool EvaluateCommandlineArguments(ref SignalingSettings settings, string[] arguments) { if (!CommandLineParser.TryParse(arguments)) return false; string signalingTypeName = null; if (CommandLineParser.SignalingType.Value != null) { signalingTypeName = CommandLineParser.SignalingType; } else if (CommandLineParser.ImportJson.Value != null) { signalingTypeName = CommandLineParser.ImportJson.Value.Value.signalingType; } if (signalingTypeName != null) { Type[] types = RuntimeTypeCache.GetTypesDerivedFrom(); Dictionary map = types.Where(type => type.GetCustomAttribute() != null) .ToDictionary(type => type.GetCustomAttribute().typename, type => type); if (map.ContainsKey(signalingTypeName)) { var type = map[signalingTypeName]; settings = (SignalingSettings)Activator.CreateInstance(type); } } return settings.ParseArguments(arguments); } /// /// Stops the signaling process. /// /// /// /// signalingManager.Stop(); /// /// public void Stop() { m_instance?.Dispose(); m_instance = null; m_running = false; } void Awake() { if (!runOnAwake || m_running || handlers.Count == 0) return; var settings = m_useDefault ? RenderStreaming.GetSignalingSettings() : signalingSettings; int i = 0; RTCIceServer[] iceServers = new RTCIceServer[settings.iceServers.Count()]; foreach (var iceServer in settings.iceServers) { iceServers[i] = (RTCIceServer)iceServer; i++; } RTCConfiguration conf = new RTCConfiguration { iceServers = iceServers }; ISignaling signaling = CreateSignaling(settings, SynchronizationContext.Current); _Run(conf, signaling, handlers.ToArray()); } void OnDestroy() { Stop(); } } }