diff --git a/Assets/Scripts/ConfigureNavBar.cs b/Assets/Scripts/ConfigureNavBar.cs
index 49ac50b..82a7220 100644
--- a/Assets/Scripts/ConfigureNavBar.cs
+++ b/Assets/Scripts/ConfigureNavBar.cs
@@ -2,35 +2,38 @@ using System.Collections.Generic;
using TMPro;
using UnityEngine;
-public class ConfigureNavBar : MonoBehaviour
+namespace WebViewStream
{
- [SerializeField]
- private EndpointLoader endpointLoader;
-
- private bool isVisible = false;
-
- ///
- /// Toggles the visibility of the address field in the nav bar.
- ///
- public void ToggleVisibilityMethod()
+ public class ConfigureNavBar : MonoBehaviour
{
- List canvases = endpointLoader.GetInstantiatedItems();
- isVisible = !isVisible;
- foreach (GameObject canvas in canvases)
- {
- TMP_InputField inputField = canvas.GetComponentInChildren(true);
- if (inputField != null)
- {
- Debug.Log("Setting address field visibility to " + isVisible);
- inputField.gameObject.SetActive(isVisible);
- }
+ [SerializeField]
+ private EndpointLoader endpointLoader;
- BoxCollider boxCollider = canvas.GetComponent();
- if (boxCollider != null)
+ private bool isVisible = false;
+
+ ///
+ /// Toggles the visibility of the address field in the nav bar.
+ ///
+ public void ToggleVisibilityMethod()
+ {
+ List canvases = endpointLoader.GetInstantiatedItems();
+ isVisible = !isVisible;
+ foreach (GameObject canvas in canvases)
{
- boxCollider.size = new Vector3(boxCollider.size.x, isVisible ? 400 : 370, boxCollider.size.z);
- boxCollider.center = new Vector3(0, isVisible ? 0 : -16, 0);
+ TMP_InputField inputField = canvas.GetComponentInChildren(true);
+ if (inputField != null)
+ {
+ Debug.Log("Setting address field visibility to " + isVisible);
+ inputField.gameObject.SetActive(isVisible);
+ }
+
+ BoxCollider boxCollider = canvas.GetComponent();
+ if (boxCollider != null)
+ {
+ boxCollider.size = new Vector3(boxCollider.size.x, isVisible ? 400 : 370, boxCollider.size.z);
+ boxCollider.center = new Vector3(0, isVisible ? 0 : -16, 0);
+ }
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/ConfigureObservers.cs b/Assets/Scripts/ConfigureObservers.cs
index 5fc7c2e..13a15b3 100644
--- a/Assets/Scripts/ConfigureObservers.cs
+++ b/Assets/Scripts/ConfigureObservers.cs
@@ -2,23 +2,26 @@ using Microsoft.MixedReality.Toolkit;
using Microsoft.MixedReality.Toolkit.SpatialAwareness;
using UnityEngine;
-public class ConfigureObservers : MonoBehaviour
+namespace WebViewStream
{
- private IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem;
-
- private void Start()
+ public class ConfigureObservers : MonoBehaviour
{
- spatialAwarenessSystem =
- MixedRealityToolkit.Instance.GetService();
+ private IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem;
- if (spatialAwarenessSystem != null)
+ private void Start()
{
- spatialAwarenessSystem.SuspendObservers();
- Debug.Log("Spatial observers suspended");
- }
- else
- {
- Debug.LogWarning("SAS is not available");
+ spatialAwarenessSystem =
+ MixedRealityToolkit.Instance.GetService();
+
+ if (spatialAwarenessSystem != null)
+ {
+ spatialAwarenessSystem.SuspendObservers();
+ Debug.Log("Spatial observers suspended");
+ }
+ else
+ {
+ Debug.LogWarning("SAS is not available");
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/ConfigureOrbital.cs b/Assets/Scripts/ConfigureOrbital.cs
index 38cc421..af30b78 100644
--- a/Assets/Scripts/ConfigureOrbital.cs
+++ b/Assets/Scripts/ConfigureOrbital.cs
@@ -2,85 +2,88 @@ using System.Collections.Generic;
using Microsoft.MixedReality.Toolkit.Utilities.Solvers;
using UnityEngine;
-public class ConfigureOrbital : MonoBehaviour
+namespace WebViewStream
{
- [SerializeField]
- private EndpointLoader endpointLoader;
-
- private bool orbitalEnabled = false;
-
- ///
- /// Toggles the orbital behavior (solver) of the canvases.
- ///
- public void ToggleOrbital()
+ public class ConfigureOrbital : MonoBehaviour
{
- orbitalEnabled = !orbitalEnabled;
- List canvases = endpointLoader.GetInstantiatedItems();
+ [SerializeField]
+ private EndpointLoader endpointLoader;
- foreach (GameObject canvas in canvases)
+ private bool orbitalEnabled = false;
+
+ ///
+ /// Toggles the orbital behavior (solver) of the canvases.
+ ///
+ public void ToggleOrbital()
{
- Orbital orbital = canvas.GetComponent();
- SolverHandler solverHandler = canvas.GetComponent();
+ orbitalEnabled = !orbitalEnabled;
+ List canvases = endpointLoader.GetInstantiatedItems();
- if (orbital != null && solverHandler != null)
+ foreach (GameObject canvas in canvases)
{
- orbital.enabled = orbitalEnabled;
+ Orbital orbital = canvas.GetComponent();
+ SolverHandler solverHandler = canvas.GetComponent();
- if (orbitalEnabled)
+ if (orbital != null && solverHandler != null)
{
- Vector3 headPosition = Camera.main.transform.position;
- Quaternion headRotation = Camera.main.transform.rotation;
- Vector3 relativePosition =
- Quaternion.Inverse(headRotation) * (orbital.transform.position - headPosition);
+ orbital.enabled = orbitalEnabled;
- orbital.LocalOffset = relativePosition;
+ if (orbitalEnabled)
+ {
+ Vector3 headPosition = Camera.main.transform.position;
+ Quaternion headRotation = Camera.main.transform.rotation;
+ Vector3 relativePosition =
+ Quaternion.Inverse(headRotation) * (orbital.transform.position - headPosition);
- solverHandler.UpdateSolvers = true;
- }
- else
- {
- solverHandler.UpdateSolvers = false;
+ orbital.LocalOffset = relativePosition;
+
+ solverHandler.UpdateSolvers = true;
+ }
+ else
+ {
+ solverHandler.UpdateSolvers = false;
+ }
}
}
}
- }
- ///
- /// Rotates the canvases to face the user.
- ///
- public void RotateCanvasToFaceUser()
- {
- List canvases = endpointLoader.GetInstantiatedItems();
-
- foreach (GameObject canvas in canvases)
+ ///
+ /// Rotates the canvases to face the user.
+ ///
+ public void RotateCanvasToFaceUser()
{
- Vector3 directionToCamera = canvas.transform.position - Camera.main.transform.position;
- canvas.transform.rotation = Quaternion.LookRotation(directionToCamera);
+ List canvases = endpointLoader.GetInstantiatedItems();
+
+ foreach (GameObject canvas in canvases)
+ {
+ Vector3 directionToCamera = canvas.transform.position - Camera.main.transform.position;
+ canvas.transform.rotation = Quaternion.LookRotation(directionToCamera);
+ }
+ }
+
+ ///
+ /// Centers the canvases in front of the user.
+ ///
+ public void CenterCanvasesToUser()
+ {
+ List canvases = endpointLoader.GetInstantiatedItems();
+
+ Vector3 localOffset = new Vector3(-0.4f, 0.1f, 1f);
+
+ foreach (GameObject canvas in canvases)
+ {
+ Transform cameraTransform = Camera.main.transform;
+
+ canvas.transform.position =
+ cameraTransform.position + cameraTransform.TransformDirection(localOffset);
+ canvas.transform.rotation = Quaternion.LookRotation(cameraTransform.forward, cameraTransform.up);
+
+ localOffset = new Vector3(
+ localOffset.x + endpointLoader.GetItemWidth(canvas),
+ localOffset.y,
+ localOffset.z
+ );
+ }
}
}
-
- ///
- /// Centers the canvases in front of the user.
- ///
- public void CenterCanvasesToUser()
- {
- List canvases = endpointLoader.GetInstantiatedItems();
-
- Vector3 localOffset = new Vector3(-0.4f, 0.1f, 1f);
-
- foreach (GameObject canvas in canvases)
- {
- Transform cameraTransform = Camera.main.transform;
-
- canvas.transform.position =
- cameraTransform.position + cameraTransform.TransformDirection(localOffset);
- canvas.transform.rotation = Quaternion.LookRotation(cameraTransform.forward, cameraTransform.up);
-
- localOffset = new Vector3(
- localOffset.x + endpointLoader.GetItemWidth(canvas),
- localOffset.y,
- localOffset.z
- );
- }
- }
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/ConfigurePointer.cs b/Assets/Scripts/ConfigurePointer.cs
index c00378a..0b76b67 100644
--- a/Assets/Scripts/ConfigurePointer.cs
+++ b/Assets/Scripts/ConfigurePointer.cs
@@ -1,34 +1,37 @@
using Microsoft.MixedReality.Toolkit.Input;
using UnityEngine;
-public class ConfigurePointer : MonoBehaviour
+namespace WebViewStream
{
- private bool handRayPointerEnabled = true;
-
- ///
- /// Toggles the hand ray pointer on and off.
- ///
- public void ToggleHandRayPointer()
+ public class ConfigurePointer : MonoBehaviour
{
- if (handRayPointerEnabled)
+ private bool handRayPointerEnabled = true;
+
+ ///
+ /// Toggles the hand ray pointer on and off.
+ ///
+ public void ToggleHandRayPointer()
{
- DisableHandRayPointer();
- handRayPointerEnabled = false;
+ if (handRayPointerEnabled)
+ {
+ DisableHandRayPointer();
+ handRayPointerEnabled = false;
+ }
+ else
+ {
+ EnableHandRayPointer();
+ handRayPointerEnabled = true;
+ }
}
- else
+
+ private void EnableHandRayPointer()
{
- EnableHandRayPointer();
- handRayPointerEnabled = true;
+ PointerUtils.SetHandRayPointerBehavior(PointerBehavior.AlwaysOn);
+ }
+
+ private void DisableHandRayPointer()
+ {
+ PointerUtils.SetHandRayPointerBehavior(PointerBehavior.AlwaysOff);
}
}
-
- private void EnableHandRayPointer()
- {
- PointerUtils.SetHandRayPointerBehavior(PointerBehavior.AlwaysOn);
- }
-
- private void DisableHandRayPointer()
- {
- PointerUtils.SetHandRayPointerBehavior(PointerBehavior.AlwaysOff);
- }
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/DialogHandler.cs b/Assets/Scripts/DialogHandler.cs
index e2e4a21..f28dc68 100644
--- a/Assets/Scripts/DialogHandler.cs
+++ b/Assets/Scripts/DialogHandler.cs
@@ -2,35 +2,38 @@ using System;
using Microsoft.MixedReality.Toolkit.UI;
using UnityEngine;
-public class DialogHandler : MonoBehaviour
+namespace WebViewStream
{
- [SerializeField]
- private GameObject dialogPrefab;
-
- ///
- /// Opens a dialog with a title, question, and action.
- ///
- ///
- ///
- ///
- public void OpenDialog(string title, string question, Action action)
+ public class DialogHandler : MonoBehaviour
{
- Dialog dialog = Dialog.Open(
- dialogPrefab,
- DialogButtonType.Yes | DialogButtonType.No,
- title,
- question,
- true
- );
- if (dialog != null)
+ [SerializeField]
+ private GameObject dialogPrefab;
+
+ ///
+ /// Opens a dialog with a title, question, and action.
+ ///
+ ///
+ ///
+ ///
+ public void OpenDialog(string title, string question, Action action)
{
- dialog.OnClosed += (x) =>
+ Dialog dialog = Dialog.Open(
+ dialogPrefab,
+ DialogButtonType.Yes | DialogButtonType.No,
+ title,
+ question,
+ true
+ );
+ if (dialog != null)
{
- if (x.Result == DialogButtonType.Yes)
+ dialog.OnClosed += (x) =>
{
- action?.Invoke();
- }
- };
+ if (x.Result == DialogButtonType.Yes)
+ {
+ action?.Invoke();
+ }
+ };
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/EndpointLoader.cs b/Assets/Scripts/EndpointLoader.cs
index d2c9af6..21be470 100644
--- a/Assets/Scripts/EndpointLoader.cs
+++ b/Assets/Scripts/EndpointLoader.cs
@@ -5,341 +5,344 @@ using Microsoft.MixedReality.WebView;
using UnityEngine;
using UnityEngine.Networking;
-public class EndpointLoader : MonoBehaviour
+namespace WebViewStream
{
- [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 bool areItemsVisible = true;
-
- 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()
+ public class EndpointLoader : MonoBehaviour
{
- apiUrl = defaultApiUrl;
- StartCoroutine(TimeoutFallback());
- StartCoroutine(LoadEndpoints());
- }
+ [SerializeField]
+ private GameObject dynamicItem;
- private IEnumerator TimeoutFallback()
- {
- float timer = 0f;
+ [SerializeField]
+ private ServiceDiscovery serviceDiscovery;
- while (timer < loadTimeout && availableServices.Count == 0)
+ [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 bool areItemsVisible = true;
+
+ private const float loadTimeout = 20f;
+ 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()
{
- yield return new WaitForSeconds(1f);
- timer += 1f;
+ apiUrl = defaultApiUrl;
+ StartCoroutine(TimeoutFallback());
+ StartCoroutine(LoadEndpoints());
}
- if (availableServices.Count == 0)
+ private IEnumerator TimeoutFallback()
{
- 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());
- }
- );
+ 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()
- {
- Transform cameraTransform = Camera.main.transform;
- Vector3 localOffset = new Vector3(-0.4f, 0.1f, 1f);
-
- if (instantiatedItems.Count == 0)
+ private Vector3 CalculateNextPosition()
{
+ Transform cameraTransform = Camera.main.transform;
+ Vector3 localOffset = new Vector3(-0.4f, 0.1f, 1f);
+
+ if (instantiatedItems.Count == 0)
+ {
+ return cameraTransform.position + cameraTransform.TransformDirection(localOffset);
+ }
+
+ GameObject lastItem = instantiatedItems[instantiatedItems.Count - 1];
+ localOffset = new Vector3(localOffset.x + GetItemWidth(lastItem), localOffset.y, localOffset.z);
return cameraTransform.position + cameraTransform.TransformDirection(localOffset);
}
- GameObject lastItem = instantiatedItems[instantiatedItems.Count - 1];
- localOffset = new Vector3(localOffset.x + GetItemWidth(lastItem), localOffset.y, localOffset.z);
- return cameraTransform.position + cameraTransform.TransformDirection(localOffset);
- }
-
- ///
- /// Toggles the visibility of the items spawned by this script.
- ///
- public void ToggleItemsVisibility()
- {
- areItemsVisible = !areItemsVisible;
- foreach (var item in instantiatedItems)
+ ///
+ /// Toggles the visibility of the items spawned by this script.
+ ///
+ public void ToggleItemsVisibility()
{
- item.SetActive(areItemsVisible);
- }
- }
-
- ///
- /// Returns the width of the item in world space units.
- ///
- ///
- ///
- public float GetItemWidth(GameObject item)
- {
- RectTransform rectTransform = item.GetComponent();
- if (rectTransform != null)
- {
- return rectTransform.rect.width * rectTransform.lossyScale.x + 0.2f;
- }
-
- return 0.8f;
- }
-
- ///
- /// Spawns a new item with a WebView component and loads the specified URL.
- ///
- ///
- public void SpawnItem(string url)
- {
- if (dynamicItem != null)
- {
- Vector3 nextPosition = CalculateNextPosition();
- Transform cameraTransform = Camera.main.transform;
- Quaternion rotation = Quaternion.LookRotation(cameraTransform.forward, cameraTransform.up);
-
- GameObject newItem = Instantiate(
- dynamicItem,
- nextPosition,
- rotation,
- dynamicItem.transform.parent
- );
- newItem.SetActive(true);
-
- instantiatedItems.Add(newItem);
-
- var webView = newItem.GetComponentInChildren();
-
- if (webView != null)
+ areItemsVisible = !areItemsVisible;
+ foreach (var item in instantiatedItems)
{
- webView.Load(url);
+ item.SetActive(areItemsVisible);
}
}
- else
+
+ ///
+ /// Returns the width of the item in world space units.
+ ///
+ ///
+ ///
+ public float GetItemWidth(GameObject item)
{
- Debug.LogError("Dynamic item is not assigned.");
- }
- }
-
- ///
- /// Returns a list of all items instantiated by this script.
- ///
- ///
- 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)
+ RectTransform rectTransform = item.GetComponent();
+ if (rectTransform != null)
{
- Debug.LogError("Multicast also failed");
+ return rectTransform.rect.width * rectTransform.lossyScale.x + 0.2f;
+ }
+
+ return 0.8f;
+ }
+
+ ///
+ /// Spawns a new item with a WebView component and loads the specified URL.
+ ///
+ ///
+ public void SpawnItem(string url)
+ {
+ if (dynamicItem != null)
+ {
+ Vector3 nextPosition = CalculateNextPosition();
+ Transform cameraTransform = Camera.main.transform;
+ Quaternion rotation = Quaternion.LookRotation(cameraTransform.forward, cameraTransform.up);
+
+ GameObject newItem = Instantiate(
+ dynamicItem,
+ nextPosition,
+ 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.");
+ }
+ }
+
+ ///
+ /// Returns a list of all items instantiated by this script.
+ ///
+ ///
+ 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;
}
- 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
- {
- if (instantiatedItems.Count > 0)
+ if (defaultEndpointLoaded)
{
- foreach (var item in instantiatedItems)
- {
- Destroy(item);
- }
- instantiatedItems.Clear();
+ Debug.Log("Default endpoint already loaded");
+ yield break;
}
- foreach (var endpoint in endpoints)
+ 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
+ )
{
- if (endpoint.url == null || endpoint.url.Length == 0)
+ Debug.LogWarning($"Error loading endpoints: {request.error}");
+
+ if (triedMulticast)
{
- Debug.LogWarning($"Endpoint URL is null for endpoint");
- continue;
+ Debug.LogError("Multicast also failed");
+ yield break;
}
- SpawnItem(endpoint.url);
+
+ Debug.LogWarning("Trying to load from default endpoints");
+ yield return StartCoroutine(TryLoadingFromDefaultEndpoints());
}
- }
- }
- private void StartListeningForMulticast()
- {
- Debug.Log("Starting multicast discovery for endpoints");
-
- triedMulticast = true;
- serviceDiscovery.StartListening(
- (service) =>
+ if (defaultEndpointLoaded)
{
- bool wasAdded = availableServices.Add(service);
- if (wasAdded)
+ 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
+ {
+ if (instantiatedItems.Count > 0)
{
- AddServiceToTable(service);
+ foreach (var item in instantiatedItems)
+ {
+ Destroy(item);
+ }
+ instantiatedItems.Clear();
+ }
+
+ 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 AddServiceToTable(MdnsService service)
- {
- servicesListPopulator.AddItemFromService(
- service,
- () =>
- {
- apiUrl = $"http://{service.IpAddress}:{service.Port}{service.Path}";
- StartCoroutine(LoadEndpoints());
- }
- );
- }
-
- ///
- /// Clears the list of available services.
- ///
- public void ClearServices()
- {
- availableServices.Clear();
- servicesListPopulator.RemoveAllItems();
- }
-
- ///
- /// Reloads the list of available services.
- ///
- 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)
+ private void StartListeningForMulticast()
{
- Wrapper wrapper = JsonUtility.FromJson>(json);
- return wrapper.Items;
+ 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());
+ }
+ );
+ }
+
+ ///
+ /// Clears the list of available services.
+ ///
+ public void ClearServices()
+ {
+ availableServices.Clear();
+ servicesListPopulator.RemoveAllItems();
+ }
+
+ ///
+ /// Reloads the list of available services.
+ ///
+ public void ReloadEndpoints()
+ {
+ triedMulticast = false;
+ StartCoroutine(LoadEndpoints());
}
[Serializable]
- private class Wrapper
+ public class Endpoint
{
- public T[] Items;
+ 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;
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/MdnsService.cs b/Assets/Scripts/MdnsService.cs
index c9bf4f5..bda15d4 100644
--- a/Assets/Scripts/MdnsService.cs
+++ b/Assets/Scripts/MdnsService.cs
@@ -1,46 +1,49 @@
-public class MdnsService
+namespace WebViewStream
{
- public string IpAddress { get; }
- public int Port { get; }
- public string Path { get; }
- public string Host { get; }
-
- ///
- /// Represents a service discovered via mDNS.
- ///
- ///
- ///
- ///
- ///
- public MdnsService(string ipAddress, int port, string path, string host)
+ public class MdnsService
{
- IpAddress = ipAddress;
- Port = port;
- Path = path;
- Host = host;
- }
+ public string IpAddress { get; }
+ public int Port { get; }
+ public string Path { get; }
+ public string Host { get; }
- public override string ToString()
- {
- return $"IpAddress: {IpAddress}, Port: {Port}, Path: {Path}, Host: {Host}";
- }
+ ///
+ /// Represents a service discovered via mDNS.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public MdnsService(string ipAddress, int port, string path, string host)
+ {
+ IpAddress = ipAddress;
+ Port = port;
+ Path = path;
+ Host = host;
+ }
- public override bool Equals(object obj)
- {
- return obj is MdnsService service
- && IpAddress == service.IpAddress
- && Host == service.Host
- && Port == service.Port
- && Path == service.Path;
- }
+ public override string ToString()
+ {
+ return $"IpAddress: {IpAddress}, Port: {Port}, Path: {Path}, Host: {Host}";
+ }
- public override int GetHashCode()
- {
- int hash = 17;
- hash = hash * 31 + (IpAddress?.GetHashCode() ?? 0);
- hash = hash * 31 + Port.GetHashCode();
- hash = hash * 31 + (Path?.GetHashCode() ?? 0);
- hash = hash * 31 + (Host?.GetHashCode() ?? 0);
- return hash;
+ public override bool Equals(object obj)
+ {
+ return obj is MdnsService service
+ && IpAddress == service.IpAddress
+ && Host == service.Host
+ && Port == service.Port
+ && Path == service.Path;
+ }
+
+ public override int GetHashCode()
+ {
+ int hash = 17;
+ hash = hash * 31 + (IpAddress?.GetHashCode() ?? 0);
+ hash = hash * 31 + Port.GetHashCode();
+ hash = hash * 31 + (Path?.GetHashCode() ?? 0);
+ hash = hash * 31 + (Host?.GetHashCode() ?? 0);
+ return hash;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/ScrollablePagination.cs b/Assets/Scripts/ScrollablePagination.cs
index 5efa50f..177109d 100644
--- a/Assets/Scripts/ScrollablePagination.cs
+++ b/Assets/Scripts/ScrollablePagination.cs
@@ -1,23 +1,26 @@
using Microsoft.MixedReality.Toolkit.UI;
using UnityEngine;
-public class ScrollablePagination : MonoBehaviour
+namespace WebViewStream
{
- [SerializeField]
- private ScrollingObjectCollection scrollView;
-
- ///
- /// Scrolls the collection by a specified amount.
- ///
- ///
- public void ScrollByTier(int amount)
+ public class ScrollablePagination : MonoBehaviour
{
- if (scrollView == null)
- {
- Debug.LogError("ScrollingObjectCollection is not set.");
- return;
- }
+ [SerializeField]
+ private ScrollingObjectCollection scrollView;
- scrollView.MoveByTiers(amount);
+ ///
+ /// Scrolls the collection by a specified amount.
+ ///
+ ///
+ public void ScrollByTier(int amount)
+ {
+ if (scrollView == null)
+ {
+ Debug.LogError("ScrollingObjectCollection is not set.");
+ return;
+ }
+
+ scrollView.MoveByTiers(amount);
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/ServiceDiscovery.cs b/Assets/Scripts/ServiceDiscovery.cs
index 8ad87ce..bd350df 100644
--- a/Assets/Scripts/ServiceDiscovery.cs
+++ b/Assets/Scripts/ServiceDiscovery.cs
@@ -6,367 +6,370 @@ using System.Net.Sockets;
using System.Text;
using UnityEngine;
-public class ServiceDiscovery : MonoBehaviour
+namespace WebViewStream
{
- private UdpClient udpClient;
- private Action action;
-
- private string receivedIp;
- private string receivedPort;
- private string receivedPath;
- private string receivedHost;
-
- private IPAddress defaultIP;
-
- private const string multicastAddress = "224.0.0.251";
- private const int multicastPort = 5353;
-
- private Queue serviceQueue = new Queue();
-
- private IPAddress GetDefaultInterfaceIP()
+ public class ServiceDiscovery : MonoBehaviour
{
- foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
+ private UdpClient udpClient;
+ private Action action;
+
+ private string receivedIp;
+ private string receivedPort;
+ private string receivedPath;
+ private string receivedHost;
+
+ private IPAddress defaultIP;
+
+ private const string multicastAddress = "224.0.0.251";
+ private const int multicastPort = 5353;
+
+ private Queue serviceQueue = new Queue();
+
+ private IPAddress GetDefaultInterfaceIP()
{
- if (ni.OperationalStatus == OperationalStatus.Up)
+ foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
{
- var ipProps = ni.GetIPProperties();
- if (ipProps.GatewayAddresses.Count > 0)
+ if (ni.OperationalStatus == OperationalStatus.Up)
{
- foreach (UnicastIPAddressInformation ip in ipProps.UnicastAddresses)
+ var ipProps = ni.GetIPProperties();
+ if (ipProps.GatewayAddresses.Count > 0)
{
- if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
+ foreach (UnicastIPAddressInformation ip in ipProps.UnicastAddresses)
{
- return ip.Address;
+ if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ return ip.Address;
+ }
}
}
}
}
+
+ return null;
}
- return null;
- }
-
- private List GetRoutableLocalIPs()
- {
- List localIPs = new List();
-
- foreach (IPAddress local in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
+ private List GetRoutableLocalIPs()
{
- if (local.AddressFamily == AddressFamily.InterNetwork)
+ List localIPs = new List();
+
+ foreach (IPAddress local in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
- byte[] bytes = local.GetAddressBytes();
- if (bytes[0] == 169 && bytes[1] == 254)
+ if (local.AddressFamily == AddressFamily.InterNetwork)
{
- Debug.Log($"Skipping non-routable address: {local}");
- continue;
+ byte[] bytes = local.GetAddressBytes();
+ if (bytes[0] == 169 && bytes[1] == 254)
+ {
+ Debug.Log($"Skipping non-routable address: {local}");
+ continue;
+ }
+
+ localIPs.Add(local);
+ }
+ }
+
+ return localIPs;
+ }
+
+ ///
+ /// Starts listening for mDNS service announcements.
+ ///
+ ///
+ public void StartListening(Action action)
+ {
+ try
+ {
+ defaultIP = GetDefaultInterfaceIP();
+ if (defaultIP == null)
+ {
+ Debug.LogError("No default interface found. Cannot start multicast listener.");
+ return;
}
- localIPs.Add(local);
+ udpClient = new UdpClient();
+ udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ udpClient.Client.Bind(new IPEndPoint(defaultIP, multicastPort));
+ udpClient.JoinMulticastGroup(IPAddress.Parse(multicastAddress), defaultIP);
+
+ this.action = action;
+
+ Debug.Log("Listening for service announcements...");
+
+ SendMdnsQuery("_http._tcp.local");
+
+ udpClient.BeginReceive(OnReceive, null);
}
- }
-
- return localIPs;
- }
-
- ///
- /// Starts listening for mDNS service announcements.
- ///
- ///
- public void StartListening(Action action)
- {
- try
- {
- defaultIP = GetDefaultInterfaceIP();
- if (defaultIP == null)
+ catch (Exception ex)
{
- Debug.LogError("No default interface found. Cannot start multicast listener.");
- return;
+ Debug.LogError($"Error starting UDP listener: {ex.Message}");
}
-
- udpClient = new UdpClient();
- udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- udpClient.Client.Bind(new IPEndPoint(defaultIP, multicastPort));
- udpClient.JoinMulticastGroup(IPAddress.Parse(multicastAddress), defaultIP);
-
- this.action = action;
-
- Debug.Log("Listening for service announcements...");
-
- SendMdnsQuery("_http._tcp.local");
-
- udpClient.BeginReceive(OnReceive, null);
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error starting UDP listener: {ex.Message}");
- }
- }
-
- private void SendMdnsQuery(string serviceName)
- {
- byte[] query = CreateMdnsQuery(serviceName);
- Debug.Log($"Sending mDNS query for {serviceName}");
-
- udpClient.Send(query, query.Length, new IPEndPoint(IPAddress.Parse(multicastAddress), multicastPort));
- }
-
- private byte[] CreateMdnsQuery(string serviceName)
- {
- ushort transactionId = 0;
- ushort flags = 0x0100;
- ushort questions = 1;
- byte[] header = new byte[12];
- Array.Copy(
- BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)transactionId)),
- 0,
- header,
- 0,
- 2
- );
- Array.Copy(
- BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)flags)),
- 0,
- header,
- 2,
- 2
- );
- Array.Copy(
- BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)questions)),
- 0,
- header,
- 4,
- 2
- );
-
- byte[] name = EncodeName(serviceName);
- byte[] query = new byte[header.Length + name.Length + 4];
- Array.Copy(header, query, header.Length);
- Array.Copy(name, 0, query, header.Length, name.Length);
-
- query[query.Length - 4] = 0x00;
- query[query.Length - 3] = 0x0C;
- query[query.Length - 2] = 0x00;
- query[query.Length - 1] = 0x01;
-
- return query;
- }
-
- private byte[] EncodeName(string name)
- {
- string[] parts = name.Split('.');
- byte[] result = new byte[name.Length + 2];
- int offset = 0;
-
- foreach (string part in parts)
- {
- result[offset++] = (byte)part.Length;
- Array.Copy(Encoding.UTF8.GetBytes(part), 0, result, offset, part.Length);
- offset += part.Length;
}
- result[offset] = 0;
- return result;
- }
-
- private void OnReceive(IAsyncResult result)
- {
- if (udpClient == null)
+ private void SendMdnsQuery(string serviceName)
{
- return;
+ byte[] query = CreateMdnsQuery(serviceName);
+ Debug.Log($"Sending mDNS query for {serviceName}");
+
+ udpClient.Send(query, query.Length, new IPEndPoint(IPAddress.Parse(multicastAddress), multicastPort));
}
- try
+ private byte[] CreateMdnsQuery(string serviceName)
{
- IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, multicastPort);
- byte[] receivedBytes = udpClient.EndReceive(result, ref remoteEndPoint);
-
- ushort flags = BitConverter.ToUInt16(new byte[] { receivedBytes[3], receivedBytes[2] }, 0);
- if (flags == 0x0100)
- {
- udpClient?.BeginReceive(OnReceive, null);
- return;
- }
-
- ParseMdnsResponse(receivedBytes);
-
- udpClient?.BeginReceive(OnReceive, null);
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error receiving UDP message: {ex.Message}");
- }
- }
-
- private void AddMdnsService()
- {
- if (receivedIp != null && receivedPort != null && receivedHost != null && receivedPath != null)
- {
- MdnsService currentService = new MdnsService(
- receivedIp,
- int.Parse(receivedPort),
- receivedPath,
- receivedHost
+ ushort transactionId = 0;
+ ushort flags = 0x0100;
+ ushort questions = 1;
+ byte[] header = new byte[12];
+ Array.Copy(
+ BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)transactionId)),
+ 0,
+ header,
+ 0,
+ 2
+ );
+ Array.Copy(
+ BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)flags)),
+ 0,
+ header,
+ 2,
+ 2
+ );
+ Array.Copy(
+ BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)questions)),
+ 0,
+ header,
+ 4,
+ 2
);
- serviceQueue.Enqueue(currentService);
- Debug.Log($"Added service: {currentService}");
- receivedIp = null;
- receivedPort = null;
- receivedPath = null;
- receivedHost = null;
- }
- }
- private void ParseMdnsResponse(byte[] data)
- {
- int offset = 12;
- ushort questions = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 4));
- ushort answerRRs = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 6));
- ushort additionalRRs = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10));
+ byte[] name = EncodeName(serviceName);
+ byte[] query = new byte[header.Length + name.Length + 4];
+ Array.Copy(header, query, header.Length);
+ Array.Copy(name, 0, query, header.Length, name.Length);
- for (int i = 0; i < questions; i++)
- {
- offset = SkipName(data, offset);
- offset += 4;
+ query[query.Length - 4] = 0x00;
+ query[query.Length - 3] = 0x0C;
+ query[query.Length - 2] = 0x00;
+ query[query.Length - 1] = 0x01;
+
+ return query;
}
- for (int i = 0; i < answerRRs; i++)
+ private byte[] EncodeName(string name)
{
- offset = ParseRecord(data, offset);
- }
+ string[] parts = name.Split('.');
+ byte[] result = new byte[name.Length + 2];
+ int offset = 0;
- for (int i = 0; i < additionalRRs; i++)
- {
- offset = ParseRecord(data, offset);
- AddMdnsService();
- }
- }
-
- private int ParseRecord(byte[] data, int offset)
- {
- string name;
- (name, offset) = ReadName(data, offset);
-
- ushort recordType = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset));
- ushort recordClass = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 2));
- uint ttl = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 4));
- ushort dataLength = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 8));
- offset += 10;
-
- if (ttl == 0)
- {
- Debug.LogWarning($"Zero TTL for {name}");
- return offset + dataLength;
- }
-
- if (recordType == 1) // A Record
- {
- IPAddress ipAddress = new IPAddress(new ArraySegment(data, offset, dataLength).ToArray());
- receivedIp = ipAddress.ToString();
- receivedHost = name;
- }
- else if (recordType == 12) // PTR Record
- {
- string target;
- (target, _) = ReadName(data, offset);
- }
- else if (recordType == 33) // SRV Record
- {
- ushort priority = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset));
- ushort weight = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 2));
- ushort port = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 4));
- string target;
- (target, _) = ReadName(data, offset + 6);
- receivedPort = port.ToString();
- }
- else if (recordType == 16) // TXT Record
- {
- string txtData = Encoding.UTF8.GetString(data, offset, dataLength);
- if (txtData.Contains("path"))
+ foreach (string part in parts)
{
- receivedPath = txtData.Split('=')[1];
+ result[offset++] = (byte)part.Length;
+ Array.Copy(Encoding.UTF8.GetBytes(part), 0, result, offset, part.Length);
+ offset += part.Length;
+ }
+
+ result[offset] = 0;
+ return result;
+ }
+
+ private void OnReceive(IAsyncResult result)
+ {
+ if (udpClient == null)
+ {
+ return;
+ }
+
+ try
+ {
+ IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, multicastPort);
+ byte[] receivedBytes = udpClient.EndReceive(result, ref remoteEndPoint);
+
+ ushort flags = BitConverter.ToUInt16(new byte[] { receivedBytes[3], receivedBytes[2] }, 0);
+ if (flags == 0x0100)
+ {
+ udpClient?.BeginReceive(OnReceive, null);
+ return;
+ }
+
+ ParseMdnsResponse(receivedBytes);
+
+ udpClient?.BeginReceive(OnReceive, null);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Error receiving UDP message: {ex.Message}");
}
}
- else if (recordType == 47) // NSEC Record
- {
- // Debug.Log($"NSEC Record: {name}");
- }
- else
- {
- Debug.Log($"Unknown Record Type {recordType} for {name}");
- }
- return offset + dataLength;
- }
-
- private (string, int) ReadName(byte[] data, int offset)
- {
- StringBuilder name = new StringBuilder();
- int originalOffset = offset;
- bool jumped = false;
-
- while (data[offset] != 0)
+ private void AddMdnsService()
{
- if ((data[offset] & 0xC0) == 0xC0)
+ if (receivedIp != null && receivedPort != null && receivedHost != null && receivedPath != null)
{
- if (!jumped)
+ MdnsService currentService = new MdnsService(
+ receivedIp,
+ int.Parse(receivedPort),
+ receivedPath,
+ receivedHost
+ );
+ serviceQueue.Enqueue(currentService);
+ Debug.Log($"Added service: {currentService}");
+ receivedIp = null;
+ receivedPort = null;
+ receivedPath = null;
+ receivedHost = null;
+ }
+ }
+
+ private void ParseMdnsResponse(byte[] data)
+ {
+ int offset = 12;
+ ushort questions = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 4));
+ ushort answerRRs = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 6));
+ ushort additionalRRs = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10));
+
+ for (int i = 0; i < questions; i++)
+ {
+ offset = SkipName(data, offset);
+ offset += 4;
+ }
+
+ for (int i = 0; i < answerRRs; i++)
+ {
+ offset = ParseRecord(data, offset);
+ }
+
+ for (int i = 0; i < additionalRRs; i++)
+ {
+ offset = ParseRecord(data, offset);
+ AddMdnsService();
+ }
+ }
+
+ private int ParseRecord(byte[] data, int offset)
+ {
+ string name;
+ (name, offset) = ReadName(data, offset);
+
+ ushort recordType = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset));
+ ushort recordClass = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 2));
+ uint ttl = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 4));
+ ushort dataLength = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 8));
+ offset += 10;
+
+ if (ttl == 0)
+ {
+ Debug.LogWarning($"Zero TTL for {name}");
+ return offset + dataLength;
+ }
+
+ if (recordType == 1) // A Record
+ {
+ IPAddress ipAddress = new IPAddress(new ArraySegment(data, offset, dataLength).ToArray());
+ receivedIp = ipAddress.ToString();
+ receivedHost = name;
+ }
+ else if (recordType == 12) // PTR Record
+ {
+ string target;
+ (target, _) = ReadName(data, offset);
+ }
+ else if (recordType == 33) // SRV Record
+ {
+ ushort priority = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset));
+ ushort weight = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 2));
+ ushort port = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset + 4));
+ string target;
+ (target, _) = ReadName(data, offset + 6);
+ receivedPort = port.ToString();
+ }
+ else if (recordType == 16) // TXT Record
+ {
+ string txtData = Encoding.UTF8.GetString(data, offset, dataLength);
+ if (txtData.Contains("path"))
{
- originalOffset = offset + 2;
+ receivedPath = txtData.Split('=')[1];
}
- offset = ((data[offset] & 0x3F) << 8) | data[offset + 1];
- jumped = true;
+ }
+ else if (recordType == 47) // NSEC Record
+ {
+ // Debug.Log($"NSEC Record: {name}");
}
else
{
- int length = data[offset++];
- name.Append(Encoding.UTF8.GetString(data, offset, length) + ".");
- offset += length;
+ Debug.Log($"Unknown Record Type {recordType} for {name}");
}
+
+ return offset + dataLength;
}
- return (name.ToString().TrimEnd('.'), jumped ? originalOffset : offset + 1);
- }
-
- private int SkipName(byte[] data, int offset)
- {
- while (data[offset] != 0)
+ private (string, int) ReadName(byte[] data, int offset)
{
- if ((data[offset] & 0xC0) == 0xC0)
- {
- return offset + 2;
- }
- offset += data[offset] + 1;
- }
- return offset + 1;
- }
+ StringBuilder name = new StringBuilder();
+ int originalOffset = offset;
+ bool jumped = false;
- private void Update()
- {
- if (serviceQueue.Count > 0)
- {
- Debug.Log($"Queued services: {serviceQueue.Count}");
-
- while (serviceQueue.Count > 0)
+ while (data[offset] != 0)
{
- MdnsService service = serviceQueue.Dequeue();
- if (service == null)
+ if ((data[offset] & 0xC0) == 0xC0)
{
- continue;
+ if (!jumped)
+ {
+ originalOffset = offset + 2;
+ }
+ offset = (data[offset] & 0x3F) << 8 | data[offset + 1];
+ jumped = true;
+ }
+ else
+ {
+ int length = data[offset++];
+ name.Append(Encoding.UTF8.GetString(data, offset, length) + ".");
+ offset += length;
+ }
+ }
+
+ return (name.ToString().TrimEnd('.'), jumped ? originalOffset : offset + 1);
+ }
+
+ private int SkipName(byte[] data, int offset)
+ {
+ while (data[offset] != 0)
+ {
+ if ((data[offset] & 0xC0) == 0xC0)
+ {
+ return offset + 2;
+ }
+ offset += data[offset] + 1;
+ }
+ return offset + 1;
+ }
+
+ private void Update()
+ {
+ if (serviceQueue.Count > 0)
+ {
+ Debug.Log($"Queued services: {serviceQueue.Count}");
+
+ while (serviceQueue.Count > 0)
+ {
+ MdnsService service = serviceQueue.Dequeue();
+ if (service == null)
+ {
+ continue;
+ }
+ Debug.Log($"Invoking action with: {service}");
+ action?.Invoke(service);
}
- Debug.Log($"Invoking action with: {service}");
- action?.Invoke(service);
}
}
- }
- private void OnDestroy()
- {
- StopListening();
- }
+ private void OnDestroy()
+ {
+ StopListening();
+ }
- private void StopListening()
- {
- udpClient?.DropMulticastGroup(IPAddress.Parse(multicastAddress));
- udpClient?.Close();
- udpClient = null;
+ private void StopListening()
+ {
+ udpClient?.DropMulticastGroup(IPAddress.Parse(multicastAddress));
+ udpClient?.Close();
+ udpClient = null;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/ServicesListPopulator.cs b/Assets/Scripts/ServicesListPopulator.cs
index bdde2b8..069ee33 100644
--- a/Assets/Scripts/ServicesListPopulator.cs
+++ b/Assets/Scripts/ServicesListPopulator.cs
@@ -4,100 +4,103 @@ using Microsoft.MixedReality.Toolkit.Utilities;
using TMPro;
using UnityEngine;
-public class ServicesListPopulator : MonoBehaviour
+namespace WebViewStream
{
- [SerializeField]
- private ScrollingObjectCollection scrollView;
-
- [SerializeField]
- private GameObject dynamicItem;
-
- [SerializeField]
- private GridObjectCollection gridObjectCollection;
-
- private bool isVisible = true;
-
- private const string apiUrlPrefix = "API URL: ";
- private const string hostPrefix = "Host: ";
-
- ///
- /// Adds an item to the table from a service.
- ///
- ///
- ///
- public void AddItemFromService(MdnsService service, Action action)
+ public class ServicesListPopulator : MonoBehaviour
{
- GameObject itemInstance = Instantiate(dynamicItem, gridObjectCollection.transform);
- itemInstance.SetActive(true);
+ [SerializeField]
+ private ScrollingObjectCollection scrollView;
- Debug.Log($"Adding service to table: {service}");
- TextMeshPro[] textMeshes = itemInstance.GetComponentsInChildren();
- if (textMeshes.Length < 2)
+ [SerializeField]
+ private GameObject dynamicItem;
+
+ [SerializeField]
+ private GridObjectCollection gridObjectCollection;
+
+ private bool isVisible = true;
+
+ private const string apiUrlPrefix = "API URL: ";
+ private const string hostPrefix = "Host: ";
+
+ ///
+ /// Adds an item to the table from a service.
+ ///
+ ///
+ ///
+ public void AddItemFromService(MdnsService service, Action action)
{
- Debug.LogError("Not enough text meshes found in dynamic item");
- return;
- }
- textMeshes[0].text = $"{apiUrlPrefix}http://{service.IpAddress}:{service.Port}{service.Path}";
- textMeshes[1].text = $"{hostPrefix}{service.Host}";
- itemInstance
- .GetComponentInChildren()
- .OnClick.AddListener(() =>
+ GameObject itemInstance = Instantiate(dynamicItem, gridObjectCollection.transform);
+ itemInstance.SetActive(true);
+
+ Debug.Log($"Adding service to table: {service}");
+ TextMeshPro[] textMeshes = itemInstance.GetComponentsInChildren();
+ if (textMeshes.Length < 2)
{
- Debug.Log($"Clicked on service: {service.Host}");
- action.Invoke();
- ToggleVisibility();
- });
-
- gridObjectCollection.UpdateCollection();
- scrollView.UpdateContent();
- }
-
- ///
- /// Removes all items from the table.
- ///
- public void RemoveAllItems()
- {
- foreach (Transform child in gridObjectCollection.transform)
- {
- Destroy(child.gameObject);
- }
-
- Debug.Log("Removed all services from table");
- gridObjectCollection.UpdateCollection();
- scrollView.UpdateContent();
- }
-
- ///
- /// Removes an item from the table by service.
- ///
- ///
- public void RemoveItemByService(MdnsService service)
- {
- string apiUrl = $"{apiUrlPrefix}http://{service.IpAddress}:{service.Port}{service.Path}";
- string hostname = $"{hostPrefix}{service.Host}";
-
- foreach (Transform child in gridObjectCollection.transform)
- {
- TextMeshPro[] textMeshes = child.GetComponentsInChildren();
- if (textMeshes.Length >= 2 && textMeshes[0].text == apiUrl && textMeshes[1].text == hostname)
- {
- Debug.Log($"Removing service from table: {service}");
- Destroy(child.gameObject);
- break;
+ Debug.LogError("Not enough text meshes found in dynamic item");
+ return;
}
+ textMeshes[0].text = $"{apiUrlPrefix}http://{service.IpAddress}:{service.Port}{service.Path}";
+ textMeshes[1].text = $"{hostPrefix}{service.Host}";
+ itemInstance
+ .GetComponentInChildren()
+ .OnClick.AddListener(() =>
+ {
+ Debug.Log($"Clicked on service: {service.Host}");
+ action.Invoke();
+ ToggleVisibility();
+ });
+
+ gridObjectCollection.UpdateCollection();
+ scrollView.UpdateContent();
}
- gridObjectCollection.UpdateCollection();
- scrollView.UpdateContent();
- }
+ ///
+ /// Removes all items from the table.
+ ///
+ public void RemoveAllItems()
+ {
+ foreach (Transform child in gridObjectCollection.transform)
+ {
+ Destroy(child.gameObject);
+ }
- ///
- /// Toggles the visibility of the table.
- ///
- public void ToggleVisibility()
- {
- isVisible = !isVisible;
+ Debug.Log("Removed all services from table");
+ gridObjectCollection.UpdateCollection();
+ scrollView.UpdateContent();
+ }
- gameObject.SetActive(isVisible);
+ ///
+ /// Removes an item from the table by service.
+ ///
+ ///
+ public void RemoveItemByService(MdnsService service)
+ {
+ string apiUrl = $"{apiUrlPrefix}http://{service.IpAddress}:{service.Port}{service.Path}";
+ string hostname = $"{hostPrefix}{service.Host}";
+
+ foreach (Transform child in gridObjectCollection.transform)
+ {
+ TextMeshPro[] textMeshes = child.GetComponentsInChildren();
+ if (textMeshes.Length >= 2 && textMeshes[0].text == apiUrl && textMeshes[1].text == hostname)
+ {
+ Debug.Log($"Removing service from table: {service}");
+ Destroy(child.gameObject);
+ break;
+ }
+ }
+
+ gridObjectCollection.UpdateCollection();
+ scrollView.UpdateContent();
+ }
+
+ ///
+ /// Toggles the visibility of the table.
+ ///
+ public void ToggleVisibility()
+ {
+ isVisible = !isVisible;
+
+ gameObject.SetActive(isVisible);
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/VideoCaptureHandler.cs b/Assets/Scripts/VideoCaptureHandler.cs
index caf3055..e0152e9 100644
--- a/Assets/Scripts/VideoCaptureHandler.cs
+++ b/Assets/Scripts/VideoCaptureHandler.cs
@@ -7,13 +7,15 @@ using UnityEngine.Windows.WebCam;
using Windows.Storage;
#endif
-public class VideoCaptureHandler : MonoBehaviour
+namespace WebViewStream
{
- [SerializeField]
- private GameObject videoCaptureButton = null;
+ public class VideoCaptureHandler : MonoBehaviour
+ {
+ [SerializeField]
+ private GameObject videoCaptureButton = null;
- private Interactable videoCaptureButtonInteractable = null;
- private VideoCapture videoCapture = null;
+ private Interactable videoCaptureButtonInteractable = null;
+ private VideoCapture videoCapture = null;
#if WINDOWS_UWP && !UNITY_EDITOR
private const string freeSpace = "System.FreeSpace";
@@ -49,122 +51,123 @@ public class VideoCaptureHandler : MonoBehaviour
}
#endif
- ///
- /// Starts recording a video.
- ///
- public void StartRecordingVideo()
- {
+ ///
+ /// Starts recording a video.
+ ///
+ public void StartRecordingVideo()
+ {
#if WINDOWS_UWP && !UNITY_EDITOR
VideoCapture.CreateAsync(true, OnVideoCaptureCreated);
#else
- VideoCapture.CreateAsync(false, OnVideoCaptureCreated);
+ VideoCapture.CreateAsync(false, OnVideoCaptureCreated);
#endif
- if (videoCaptureButtonInteractable == null)
- {
- videoCaptureButtonInteractable = videoCaptureButton.GetComponent();
- }
- videoCaptureButtonInteractable.IsToggled = true;
- }
-
- ///
- /// Stops recording a video.
- ///
- public void StopRecordingVideo()
- {
- videoCapture.StopRecordingAsync(OnStoppedRecordingVideo);
- videoCaptureButtonInteractable.IsToggled = false;
- }
-
- ///
- /// Toggles the recording of a video.
- ///
- public void ToggleRecordingVideo()
- {
- if (videoCapture == null)
- {
- StartRecordingVideo();
- }
- else if (videoCapture.IsRecording)
- {
- StopRecordingVideo();
- }
- }
-
- private void OnVideoCaptureCreated(VideoCapture videoCapture)
- {
- if (videoCapture != null)
- {
- this.videoCapture = videoCapture;
-
- Resolution cameraResolution = new Resolution();
- foreach (Resolution resolution in VideoCapture.SupportedResolutions)
+ if (videoCaptureButtonInteractable == null)
{
- if (resolution.width * resolution.height > cameraResolution.width * cameraResolution.height)
- {
- cameraResolution = resolution;
- }
+ videoCaptureButtonInteractable = videoCaptureButton.GetComponent();
}
+ videoCaptureButtonInteractable.IsToggled = true;
+ }
- float cameraFramerate = 0.0f;
- foreach (float framerate in VideoCapture.GetSupportedFrameRatesForResolution(cameraResolution))
+ ///
+ /// Stops recording a video.
+ ///
+ public void StopRecordingVideo()
+ {
+ videoCapture.StopRecordingAsync(OnStoppedRecordingVideo);
+ videoCaptureButtonInteractable.IsToggled = false;
+ }
+
+ ///
+ /// Toggles the recording of a video.
+ ///
+ public void ToggleRecordingVideo()
+ {
+ if (videoCapture == null)
{
- if (framerate > cameraFramerate)
- {
- cameraFramerate = framerate;
- }
+ StartRecordingVideo();
+ }
+ else if (videoCapture.IsRecording)
+ {
+ StopRecordingVideo();
}
-
- CameraParameters cameraParameters = new CameraParameters();
- cameraParameters.hologramOpacity = 0.75f;
- cameraParameters.frameRate = cameraFramerate;
- cameraParameters.cameraResolutionWidth = cameraResolution.width;
- cameraParameters.cameraResolutionHeight = cameraResolution.height;
- cameraParameters.pixelFormat = CapturePixelFormat.BGRA32;
-
- this.videoCapture.StartVideoModeAsync(
- cameraParameters,
- VideoCapture.AudioState.ApplicationAndMicAudio,
- OnStartedVideoCaptureMode
- );
}
- else
+
+ private void OnVideoCaptureCreated(VideoCapture videoCapture)
{
- Debug.LogError("Failed to create VideoCapture instance");
- }
- }
+ if (videoCapture != null)
+ {
+ this.videoCapture = videoCapture;
- private void OnStartedVideoCaptureMode(VideoCapture.VideoCaptureResult result)
- {
- if (result.success)
+ Resolution cameraResolution = new Resolution();
+ foreach (Resolution resolution in VideoCapture.SupportedResolutions)
+ {
+ if (resolution.width * resolution.height > cameraResolution.width * cameraResolution.height)
+ {
+ cameraResolution = resolution;
+ }
+ }
+
+ float cameraFramerate = 0.0f;
+ foreach (float framerate in VideoCapture.GetSupportedFrameRatesForResolution(cameraResolution))
+ {
+ if (framerate > cameraFramerate)
+ {
+ cameraFramerate = framerate;
+ }
+ }
+
+ CameraParameters cameraParameters = new CameraParameters();
+ cameraParameters.hologramOpacity = 0.75f;
+ cameraParameters.frameRate = cameraFramerate;
+ cameraParameters.cameraResolutionWidth = cameraResolution.width;
+ cameraParameters.cameraResolutionHeight = cameraResolution.height;
+ cameraParameters.pixelFormat = CapturePixelFormat.BGRA32;
+
+ this.videoCapture.StartVideoModeAsync(
+ cameraParameters,
+ VideoCapture.AudioState.ApplicationAndMicAudio,
+ OnStartedVideoCaptureMode
+ );
+ }
+ else
+ {
+ Debug.LogError("Failed to create VideoCapture instance");
+ }
+ }
+
+ private void OnStartedVideoCaptureMode(VideoCapture.VideoCaptureResult result)
{
- string filename = string.Format(
- "WebView_{0}.mp4",
- DateTime.UtcNow.ToString("yyyy-MM-ddTHHmmssZ")
- );
- string filepath = Path.Combine(Application.persistentDataPath, filename);
- Debug.Log("Saving video to: " + filepath);
+ if (result.success)
+ {
+ string filename = string.Format(
+ "WebView_{0}.mp4",
+ DateTime.UtcNow.ToString("yyyy-MM-ddTHHmmssZ")
+ );
+ string filepath = Path.Combine(Application.persistentDataPath, filename);
+ Debug.Log("Saving video to: " + filepath);
- videoCapture.StartRecordingAsync(filepath, OnStartedRecordingVideo);
+ videoCapture.StartRecordingAsync(filepath, OnStartedRecordingVideo);
+ }
}
- }
- private void OnStartedRecordingVideo(VideoCapture.VideoCaptureResult result)
- {
- Debug.Log("Started recording video");
+ private void OnStartedRecordingVideo(VideoCapture.VideoCaptureResult result)
+ {
+ Debug.Log("Started recording video");
#if WINDOWS_UWP && !UNITY_EDITOR
StartCoroutine(CheckAvailableStorageSpace());
#endif
- }
+ }
- private void OnStoppedRecordingVideo(VideoCapture.VideoCaptureResult result)
- {
- Debug.Log("Stopped recording video");
- videoCapture.StopVideoModeAsync(OnStoppedVideoCaptureMode);
- }
+ private void OnStoppedRecordingVideo(VideoCapture.VideoCaptureResult result)
+ {
+ Debug.Log("Stopped recording video");
+ videoCapture.StopVideoModeAsync(OnStoppedVideoCaptureMode);
+ }
- private void OnStoppedVideoCaptureMode(VideoCapture.VideoCaptureResult result)
- {
- videoCapture.Dispose();
- videoCapture = null;
+ private void OnStoppedVideoCaptureMode(VideoCapture.VideoCaptureResult result)
+ {
+ videoCapture.Dispose();
+ videoCapture = null;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Assets/Scripts/WebViewBrowser.cs b/Assets/Scripts/WebViewBrowser.cs
index 9ebbe21..9aa57e5 100644
--- a/Assets/Scripts/WebViewBrowser.cs
+++ b/Assets/Scripts/WebViewBrowser.cs
@@ -3,43 +3,46 @@ using Microsoft.MixedReality.WebView;
using TMPro;
using UnityEngine;
-public class WebViewBrowser : MonoBehaviour
+namespace WebViewStream
{
- [SerializeField]
- private TMP_InputField URLField;
-
- private void Start()
+ public class WebViewBrowser : MonoBehaviour
{
- var webViewComponent = gameObject.GetComponent();
- webViewComponent.GetWebViewWhenReady(
- (IWebView webView) =>
- {
- URLField.onSubmit.AddListener((text) => LoadUrl(webView));
+ [SerializeField]
+ private TMP_InputField URLField;
- webView.Navigated += OnNavigated;
-
- if (webView.Page != null)
+ private void Start()
+ {
+ var webViewComponent = gameObject.GetComponent();
+ webViewComponent.GetWebViewWhenReady(
+ (IWebView webView) =>
{
- URLField.text = webView.Page.AbsoluteUri;
+ URLField.onSubmit.AddListener((text) => LoadUrl(webView));
+
+ webView.Navigated += OnNavigated;
+
+ if (webView.Page != null)
+ {
+ URLField.text = webView.Page.AbsoluteUri;
+ }
}
+ );
+ }
+
+ private void OnNavigated(string path)
+ {
+ URLField.text = path;
+ }
+
+ private void LoadUrl(IWebView webView)
+ {
+ if (Uri.TryCreate(URLField.text, UriKind.Absolute, out Uri uriResult))
+ {
+ webView.Load(uriResult);
+ }
+ else
+ {
+ Debug.LogWarning("Invalid URL entered.");
}
- );
- }
-
- private void OnNavigated(string path)
- {
- URLField.text = path;
- }
-
- private void LoadUrl(IWebView webView)
- {
- if (Uri.TryCreate(URLField.text, UriKind.Absolute, out Uri uriResult))
- {
- webView.Load(uriResult);
- }
- else
- {
- Debug.LogWarning("Invalid URL entered.");
}
}
-}
+}
\ No newline at end of file