using System; using System.Collections; using System.Collections.Generic; using Microsoft.MixedReality.WebView; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; public class EndpointLoader : MonoBehaviour { [SerializeField] private GameObject dynamicItem; [SerializeField] private ServiceDiscovery serviceDiscovery; [SerializeField] private ServicesListPopulator servicesListPopulator; [SerializeField] private DialogHandler dialogHandler; private string apiUrl; private bool triedMulticast = false; private bool defaultEndpointLoaded = false; private List instantiatedItems = new List(); private HashSet availableServices = new HashSet(); private float loadTimeout = 10f; private const string defaultApiUrl = "http://windows.local:5000/api/endpoints"; private const string defaultEndpoint1 = "http://windows.local:8100/mystream/"; private const string defaultEndpoint2 = "http://windows.local:8200/mystream/"; private void Start() { apiUrl = defaultApiUrl; StartCoroutine(TimeoutFallback()); StartCoroutine(LoadEndpoints()); } private IEnumerator TimeoutFallback() { float timer = 0f; while (timer < loadTimeout && availableServices.Count == 0) { yield return new WaitForSeconds(1f); timer += 1f; } if (availableServices.Count == 0) { Debug.LogWarning("Timeout reached. Loading default endpoints..."); dialogHandler.OpenDialog( "Timeout reached", "No services were found within the time limit.\r\n" + "Would you like to load the default endpoints now?\r\n" + "If you click \"No\", we will continue waiting for mDNS services to appear.", () => { StartCoroutine(TryLoadingFromDefaultEndpoints()); } ); } } private Vector3 CalculateNextPosition() { if (instantiatedItems.Count == 0) { return new Vector3(-0.4f, 0.1f, 1); } GameObject lastItem = instantiatedItems[instantiatedItems.Count - 1]; Vector3 lastPosition = lastItem.transform.position; float itemWidth = GetItemWidth(lastItem); return new Vector3(lastPosition.x + itemWidth, lastPosition.y, lastPosition.z); } private float GetItemWidth(GameObject item) { Renderer renderer = item.GetComponent(); if (renderer != null) { return renderer.bounds.size.x; } return 1.0f; } public void SpawnItem(string url) { if (dynamicItem != null) { Vector3 nextPosition = CalculateNextPosition(); GameObject newItem = Instantiate( dynamicItem, nextPosition, dynamicItem.transform.rotation, dynamicItem.transform.parent ); newItem.SetActive(true); instantiatedItems.Add(newItem); var webView = newItem.GetComponentInChildren(); if (webView != null) { webView.Load(url); } } else { Debug.LogError("Dynamic item is not assigned."); } } public List GetInstantiatedItems() { return instantiatedItems; } private IEnumerator TryLoadingFromDefaultEndpoints() { using (UnityWebRequest request = UnityWebRequest.Get(defaultEndpoint1)) { yield return request.SendWebRequest(); ProcessEndpointResponse(request, defaultEndpoint1, ref defaultEndpointLoaded); } using (UnityWebRequest request = UnityWebRequest.Get(defaultEndpoint2)) { yield return request.SendWebRequest(); ProcessEndpointResponse(request, defaultEndpoint2, ref defaultEndpointLoaded); } if (!defaultEndpointLoaded) { Debug.LogError("Failed to load default endpoints"); dialogHandler.OpenDialog( "Failed to load the default endpoints", "Do you want to try one more time?\r\n" + "If you click \"No\", we will continue waiting for mDNS services to appear.", () => { StartCoroutine(TryLoadingFromDefaultEndpoints()); } ); } } private void ProcessEndpointResponse(UnityWebRequest request, string endpoint, ref bool loadedFlag) { if ( request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError ) { Debug.LogError($"Error loading from {endpoint}: {request.error}"); } else { Debug.Log($"Loaded from {endpoint} successfully."); SpawnItem(endpoint); loadedFlag = true; } } private IEnumerator LoadEndpoints() { if (!triedMulticast) { StartListeningForMulticast(); yield break; } if (defaultEndpointLoaded) { Debug.Log("Default endpoint already loaded"); yield break; } Debug.Log($"Loading endpoints from {apiUrl}"); var request = new UnityWebRequest(apiUrl, UnityWebRequest.kHttpVerbGET); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); yield return request.SendWebRequest(); if ( request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError ) { Debug.LogWarning($"Error loading endpoints: {request.error}"); if (triedMulticast) { Debug.LogError("Multicast also failed"); yield break; } Debug.LogWarning("Trying to load from default endpoints"); yield return StartCoroutine(TryLoadingFromDefaultEndpoints()); } if (defaultEndpointLoaded) { Debug.Log("At least one default endpoint loaded successfully"); yield break; } var json = request.downloadHandler.text; json = "{\"Items\":" + json + "}"; Debug.Log($"Received JSON: {json}"); Endpoint[] endpoints = JsonHelper.FromJson(json); if (endpoints.Length == 0) { Debug.LogError("Parsed endpoints are empty"); } else { foreach (var endpoint in endpoints) { if (endpoint.url == null || endpoint.url.Length == 0) { Debug.LogWarning($"Endpoint URL is null for endpoint"); continue; } SpawnItem(endpoint.url); } } } private void StartListeningForMulticast() { Debug.Log("Starting multicast discovery for endpoints"); triedMulticast = true; serviceDiscovery.StartListening( (service) => { bool wasAdded = availableServices.Add(service); if (wasAdded) { AddServiceToTable(service); } } ); } private void AddServiceToTable(MdnsService service) { servicesListPopulator.AddItemFromService( service, () => { apiUrl = $"http://{service.IpAddress}:{service.Port}{service.Path}"; StartCoroutine(LoadEndpoints()); } ); } public void ClearServices() { availableServices.Clear(); servicesListPopulator.RemoveAllItems(); } public void ReloadEndpoints() { triedMulticast = false; StartCoroutine(LoadEndpoints()); } [Serializable] public class Endpoint { public int id; public string url; } public static class JsonHelper { public static T[] FromJson(string json) { Wrapper wrapper = JsonUtility.FromJson>(json); return wrapper.Items; } [Serializable] private class Wrapper { public T[] Items; } } }