// %BANNER_BEGIN% // --------------------------------------------------------------------- // %COPYRIGHT_BEGIN% // Copyright (c) (2018-2022) Magic Leap, Inc. All Rights Reserved. // Use of this file is governed by the 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% namespace UnityEngine.XR.MagicLeap { using System; using System.Runtime.InteropServices; using UnityEngine.XR.MagicLeap.Native; /// /// MLWebRTC class contains the API to interface with the /// WebRTC C API. /// public partial class MLWebRTC { /// /// Class that represents a data channel used by the MLWebRTC API. /// public partial class DataChannel { /// /// Native bindings for the MLWebRTC.DataChannel class. /// internal class NativeBindings : MagicLeapNativeBindings { /// /// A delegate that describes the requirements of the OnOpened callback. /// /// Pointer to a context object. public delegate void OnOpenedDelegate(IntPtr context); /// /// A delegate that describes the requirements of the OnClosed callback. /// /// Pointer to a context object. public delegate void OnClosedDelegate(IntPtr context); /// /// A delegate that describes the requirements of the OnMessage callback. /// /// The native message object received. /// Pointer to a context object. public delegate void OnMessageDelegate(in MLWebRTCDataChannelMessage message, IntPtr context); /// /// Creates a data channel. /// /// The connection to associate the data channel with. /// The label to give the data channel. /// The handle of the data channel after it is created. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCDataChannelCreate(ulong connectionHandle, [MarshalAs(UnmanagedType.LPStr)] string label, out ulong dataChannelHandle); /// /// Gets the label of a data channel, call MLWebRTCDataChannelReleaseLabelMemory after. /// /// The handle of the data channel. /// Pointer to the unmanaged label string. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCDataChannelGetLabel(ulong dataChannelHandle, out IntPtr label); /// /// Releases the memory created when calling MLWebRTCDataChannelGetLabel. /// /// The handle of the data channel. /// Pointer to the unmanaged label string. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCDataChannelReleaseLabelMemory(ulong dataChannelHandle, IntPtr label); /// /// Gets if a data channel is open. /// /// The handle of the data channel. /// True if the data channel is open. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCDataChannelIsOpen(ulong dataChannelHandle, [MarshalAs(UnmanagedType.I1)] out bool isOpen); /// /// Sets the callbacks for a data channel. /// /// The handle of the data channel. /// Native callbacks object to set the data channel with. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCDataChannelSetEventCallbacks(ulong dataChannelHandle, in MLWebRTCDataChannelEventCallbacks callbacks); /// /// Sends a message to a data channel. /// /// The handle of the data channel. /// Native message object to send to the data channel. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCDataChannelSendMessage(ulong dataChannelHandle, in MLWebRTCDataChannelMessage message); /// /// Destroys a data channel. /// /// The handle of the associated connection. /// The handle of the data channel. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCDataChannelDestroy(ulong connectionHandle, ulong dataChannelHandle); /// /// Sets the callbacks of a data channel. /// /// The data channel to reference for the callbacks. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// public static MLResult.Code SetCallbacks(MLWebRTC.DataChannel dataChannel) { dataChannel.gcHandle = GCHandle.Alloc(dataChannel); IntPtr gcHandlePtr = GCHandle.ToIntPtr(dataChannel.gcHandle); NativeBindings.MLWebRTCDataChannelEventCallbacks callbacks = NativeBindings.MLWebRTCDataChannelEventCallbacks.Create(gcHandlePtr); MLResult.Code resultCode = NativeBindings.MLWebRTCDataChannelSetEventCallbacks(dataChannel.Handle, in callbacks); if (!MLResult.DidNativeCallSucceed(resultCode, nameof(NativeBindings.MLWebRTCDataChannelSetEventCallbacks))) { dataChannel.gcHandle.Free(); } return resultCode; } /// /// Sends a string message to a data channel. /// /// Data channel to send the message to. /// The string to send. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// public static MLResult.Code SendMessageToDataChannel(MLWebRTC.DataChannel dataChannel, string message) { NativeBindings.MLWebRTCDataChannelMessage messageNative = NativeBindings.MLWebRTCDataChannelMessage.Create(message); MLResult.Code resultCode = NativeBindings.MLWebRTCDataChannelSendMessage(dataChannel.Handle, in messageNative); MLResult.DidNativeCallSucceed(resultCode, nameof(NativeBindings.MLWebRTCDataChannelSendMessage)); Marshal.FreeHGlobal(messageNative.Data); return resultCode; } /// /// Sends a string message to a data channel. /// /// Data channel to send the message to. /// The byte array to send. /// /// MLResult.Result will be MLResult.Code.Ok if destroying all handles was successful. /// MLResult.Result will be MLResult.Code.MismatchingHandle if an incorrect handle was sent. /// MLResult.Result will be MLResult.Code.PermissionDenied if necessary permission is missing. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// public static MLResult.Code SendMessageToDataChannel(MLWebRTC.DataChannel dataChannel, T[] message) { NativeBindings.MLWebRTCDataChannelMessage messageNative = NativeBindings.MLWebRTCDataChannelMessage.Create(message); MLResult.Code resultCode = NativeBindings.MLWebRTCDataChannelSendMessage(dataChannel.Handle, in messageNative); MLResult.DidNativeCallSucceed(resultCode, nameof(NativeBindings.MLWebRTCDataChannelSendMessage)); Marshal.FreeHGlobal(messageNative.Data); return resultCode; } /// /// Native callback that is invoked when a data channel opens. /// /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(OnOpenedDelegate))] private static void OnOpened(IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); DataChannel dataChannel = gcHandle.Target as DataChannel; dataChannel?.OnOpened?.Invoke(dataChannel); }); } /// /// Native callback that is invoked when a data channel closes. /// /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(OnClosedDelegate))] private static void OnClosed(IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); DataChannel dataChannel = gcHandle.Target as DataChannel; dataChannel?.OnClosed?.Invoke(dataChannel); }); } /// /// Native callback that is invoked when a data channel receives a message. /// /// Message object received. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(OnMessageDelegate))] private static void OnMessage(in NativeBindings.MLWebRTCDataChannelMessage message, IntPtr context) { object messageObject = null; bool isBinary = message.IsBinary; if (isBinary) { byte[] bytes = new byte[message.DataSize]; Marshal.Copy(message.Data, bytes, 0, (int)message.DataSize); messageObject = bytes; } else { string chars = Marshal.PtrToStringAnsi(message.Data, (int)message.DataSize); messageObject = chars; } MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); DataChannel dataChannel = gcHandle.Target as DataChannel; if (isBinary) { dataChannel?.OnMessageBinary?.Invoke(dataChannel, messageObject as byte[]); } else { dataChannel?.OnMessageText?.Invoke(dataChannel, messageObject as string); } }); } /// /// The native representation of the MLWebRTC data channel message. /// [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCDataChannelMessage { /// /// Version of the struct. /// public uint Version; /// /// Pointer to the message in unmanaged memory. /// public IntPtr Data; /// /// Describes how large the unmanaged data is. /// public ulong DataSize; /// /// Determines if the message is a string or byte array. /// [MarshalAs(UnmanagedType.I1)] public bool IsBinary; /// /// Creates an initialized MLWebRTCDataChannelMessage object. /// /// The string message to send. /// An initialized MLWebRTCDataChannelMessage object. public static MLWebRTCDataChannelMessage Create(string message) { MLWebRTCDataChannelMessage channelMessage = new MLWebRTCDataChannelMessage(); channelMessage.Version = 1; channelMessage.Data = Marshal.StringToHGlobalAnsi(message); channelMessage.DataSize = (ulong)message.Length; channelMessage.IsBinary = false; return channelMessage; } /// /// Creates an initialized MLWebRTCDataChannelMessage object. /// /// The byte array message to send. /// An initialized MLWebRTCDataChannelMessage object. public static MLWebRTCDataChannelMessage Create(T[] message) { MLWebRTCDataChannelMessage channelMessage = new MLWebRTCDataChannelMessage(); channelMessage.Version = 1; int typeSize = Marshal.SizeOf(typeof(T)); int unmanagedByteArrayLength = (typeSize * message.Length); channelMessage.Data = Marshal.AllocHGlobal(unmanagedByteArrayLength); IntPtr next = channelMessage.Data; for (int i = 0; i < message.Length; ++i) { Marshal.StructureToPtr(message[i], next, false); next = IntPtr.Add(next, typeSize); } channelMessage.DataSize = (ulong)unmanagedByteArrayLength; channelMessage.IsBinary = true; return channelMessage; } } /// /// The native representation of the MLWebRTC data channel callback events. /// [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCDataChannelEventCallbacks { /// /// Version of the struct. /// public uint Version; /// /// Context pointer used with the dataChannelCounter field to determine which data channel that callbacks are associated with. /// public IntPtr Context; /// /// Native OnOpen event. /// public OnOpenedDelegate OnOpen; /// /// Native OnClosed event. /// public OnClosedDelegate OnClosed; /// /// Native OnMessage event. /// public OnMessageDelegate OnMessage; /// /// Creates an initialized MLWebRTCDataChannelEventCallbacks object. /// /// The context object to use. /// An initialized MLWebRTCDataChannelEventCallbacks object. public static MLWebRTCDataChannelEventCallbacks Create(IntPtr context) { MLWebRTCDataChannelEventCallbacks callbacks = new MLWebRTCDataChannelEventCallbacks(); callbacks.Version = 1; callbacks.OnOpen = DataChannel.NativeBindings.OnOpened; callbacks.OnClosed = DataChannel.NativeBindings.OnClosed; callbacks.OnMessage = DataChannel.NativeBindings.OnMessage; callbacks.Context = context; return callbacks; } } } } } }