// %BANNER_BEGIN% // --------------------------------------------------------------------- // %COPYRIGHT_BEGIN% // Copyright (c) 2022-2023 Magic Leap, Inc. All Rights Reserved. // Use of this file is governed by the Magic Leap 2 Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2 // Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein. // %COPYRIGHT_END% // --------------------------------------------------------------------- // %BANNER_END% using System.Net.Sockets; using System.Net; using UnityEngine; using System; using System.Text; namespace MagicLeap.Spectator { [Serializable] public class DeviceDiscovery : MonoBehaviour { #region Public enum public struct DiscoveredDevice { public string ip; // IP of the ML2 device public string appname; // Name of the application running on the ML2 public string customid; // User specified id to identify device public string version; // Version of the spectator plugin public int state; // Check if there is already a spectator device connected (0 = available) public static DiscoveredDevice CreateFromJSON(string jsonString) { return JsonUtility.FromJson(jsonString); } public string ToJson() { return JsonUtility.ToJson(this); } } #endregion #region Singleton private static DeviceDiscovery _instance; public static DeviceDiscovery Instance { get { if (_instance == null) _instance = FindObjectOfType(true); if (_instance == null) Debug.LogWarning("DeviceDiscovery: Instance is missing or has already been destroyed"); return _instance; } } #endregion #region Private classes private class DeviceDiscoveryListener : IDisposable { public UdpClient client; public IPEndPoint endpoint; public bool isDiscoverable; public DeviceMetaDataInterface deviceMetaData = null; public event Action OnDeviceDiscovered; private SafeBool isClosing = new SafeBool(false); public DeviceDiscoveryListener(int port, bool replyDiscoveryRequests = false, DeviceMetaDataInterface metaData = null) { isDiscoverable = replyDiscoveryRequests; endpoint = new IPEndPoint(IPAddress.Any, port); deviceMetaData = metaData; try { client = new UdpClient(endpoint); client.BeginReceive(new AsyncCallback(ReceiveCallback), this); //Debug.Log($"DeviceDiscovery: DeviceDiscoveryListener() - Listening on port {port}"); } catch (Exception ex) { Debug.LogError($"DeviceDiscovery: DeviceDiscoveryListener() - {ex.Message}"); } } ~DeviceDiscoveryListener() { Dispose(); } public void Dispose() { if (client == null) return; isClosing.Set(true); client.Close(); client = null; //Debug.Log($"DeviceDiscoveryListener: Dispose()"); } public static void ReceiveCallback(IAsyncResult ar) { DeviceDiscoveryListener listener = (DeviceDiscoveryListener)(ar.AsyncState); if (listener == null) { Debug.LogError($"DeviceDiscovery: ReceiveCallback() - DeviceDiscoveryListener is invalid"); return; } UdpClient client = listener.client; if (client == null) { if (listener.isClosing) Debug.Log("DeviceDiscovery: ReceiveCallback() - Aborting due to listener closing"); else Debug.LogError($"DeviceDiscovery: ReceiveCallback() - Client is invalid"); return; } IPEndPoint endpoint = listener.endpoint; if (endpoint == null) { Debug.LogError($"DeviceDiscovery: ReceiveCallback() - Endpoint is invalid"); return; } byte[] receiveBytes = client.EndReceive(ar, ref endpoint); client.BeginReceive(new AsyncCallback(ReceiveCallback), listener); if (receiveBytes == null) { Debug.LogError($"DeviceDiscovery: ReceiveCallback() - No bytes received"); return; } Message msgReceived = Message.FromByteArray(receiveBytes); if (msgReceived.guid == SpectatorMessages.DeviceDiscoveryRequest) { //Debug.Log($"Received: DeviceDiscoveryRequest from {endpoint.Address.ToString()}"); if (listener.isDiscoverable) { Message msgSend = null; if (listener.deviceMetaData != null) { DiscoveredDevice meta = listener.deviceMetaData.GatherDeviceMetaData(); byte[] payload = Encoding.ASCII.GetBytes(listener.deviceMetaData.GatherDeviceMetaData().ToJson()); msgSend = new Message(SpectatorMessages.DeviceDiscoveryReply, payload, payload.Length); } else { Debug.LogWarning("DeviceDiscovery: ReceiveCallback() - No device meta data available)"); msgSend = SpectatorMessages.DeviceDiscoveryReply; } byte[] sendbuf = Message.ToByteArray(msgSend); client.Send(sendbuf, sendbuf.Length, new IPEndPoint(endpoint.Address, listener.endpoint.Port)); //Debug.Log($"Send: DeviceDiscoveryReply to {endpoint.Address.ToString()}"); } } else if (msgReceived.guid == SpectatorMessages.DeviceDiscoveryReply) { //Debug.Log($"Received: DeviceDiscoveryReply from {endpoint.Address.ToString()}"); if (msgReceived.size <= 0) { Debug.LogError("DeviceDiscovery:ReceiveCallback() - DeviceDiscoveryReply does not contain a payload"); listener.OnDeviceDiscovered?.Invoke(new DiscoveredDevice() { ip = endpoint.Address.ToString(), appname = "Unknown app", customid = "No id", version="unknown", state=0 }); } else { string jsonDeviceData = Encoding.ASCII.GetString(msgReceived.data); DiscoveredDevice deviceData = DiscoveredDevice.CreateFromJSON(jsonDeviceData); deviceData.ip = endpoint.Address.ToString(); // overwrite declared ip with endpoint of received packet listener.OnDeviceDiscovered?.Invoke(deviceData); } } else { Debug.LogError($"Received: from {endpoint.Address}"); } } } #endregion #region Inspector variables [SerializeField] private int port = 63520; [SerializeField] private bool isDevice = true; [SerializeField] private float waitTime = 10.0f; #endregion #region Public events public event Action OnDeviceDiscovered; #endregion #region Private members private DeviceDiscoveryListener deviceDiscoveryListener = null; private IPEndPoint endPoint = null; private DeviceMetaDataInterface deviceMetaData = null; #endregion #region MonoBehaviour methods private void OnEnable() { IPAddress localIP; if (Application.platform == RuntimePlatform.Android && !isDevice) { //First get from nativa Android, then try Unity method AndroidJavaClass DeviceScreenJavaClass = new AndroidJavaClass("com.magicleap.network.WifiInfoGathering"); string androidIp = DeviceScreenJavaClass.CallStatic("GetWifiIpv4Address"); if (!string.IsNullOrEmpty(androidIp)) localIP = IPAddress.Parse(androidIp); else if (!NetworkHelper.TryGetLocalIPAddress(out localIP, isDevice)) return; } else if (!NetworkHelper.TryGetLocalIPAddress(out localIP, isDevice)) return;// If no local IP can be identified, broadcasting is not possible IPAddress broadcastIP = NetworkHelper.GetBroadcastAddress(localIP, IPAddress.Parse("255.255.255.0")); endPoint = new IPEndPoint(broadcastIP, port); if (deviceDiscoveryListener == null) { deviceDiscoveryListener = new DeviceDiscoveryListener(port, isDevice, deviceMetaData); deviceDiscoveryListener.OnDeviceDiscovered += DeviceDiscoveryListener_OnDeviceDiscovered; } if (!isDevice) { InvokeRepeating("BroadcastDiscoveryRequest", 0, waitTime); } } private void OnDisable() { CancelInvoke(); deviceDiscoveryListener.Dispose(); deviceDiscoveryListener = null; } #endregion #region Private method private void DeviceDiscoveryListener_OnDeviceDiscovered(DiscoveredDevice discoveredDevice) { OnDeviceDiscovered?.Invoke(discoveredDevice); } private void BroadcastDiscoveryRequest() { // Cannot broadcast without an endpoint if (endPoint == null) return; Message msg = SpectatorMessages.DeviceDiscoveryRequest; byte[] sendbuf = Message.ToByteArray(msg); UdpClient sender = new UdpClient(); sender.EnableBroadcast = true; //Debug.Log($"DeviceDiscovery: BroadcastDiscoveryRequest() - Broadcast with address {endPoint.Address}"); sender.Send(sendbuf, sendbuf.Length, endPoint); sender.Dispose(); } #endregion #region Public methods public void SetDeviceMetaData(DeviceMetaDataInterface metaData) { deviceMetaData = metaData; if(deviceDiscoveryListener != null) deviceDiscoveryListener.deviceMetaData = deviceMetaData; } #endregion } }