// %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.Collections.Generic; 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 connection used by the MLWebRTC API. /// public partial class PeerConnection { /// /// Native bindings for the MLWebRTC.PeerConnection class. /// internal class NativeBindings : MagicLeapNativeBindings { /// /// A delegate that describes the requirements of the OnError callback. /// /// The error message. /// Pointer to a context object. public delegate void OnErrorDelegate([MarshalAs(UnmanagedType.LPStr)] string message, IntPtr context); /// /// A delegate that describes the requirements of the OnConnectedDelegate callback. /// /// Pointer to a context object. public delegate void OnConnectedDelegate(IntPtr context); /// /// A delegate that describes the requirements of the OnDisconnectedDelegate callback. /// /// Pointer to a context object. public delegate void OnDisconnectedDelegate(IntPtr context); /// /// A delegate that describes the requirements of the OnTrackAddedDelegate callback. /// /// The native source object that was added. /// The number of streams this track belongs to. /// The names of the streams this track belongs to. /// Pointer to a context object. public delegate void OnTrackAddedDelegate(in MLWebRTC.Source.NativeBindings.MLWebRTCSource sourceNative, uint numStreamIds, IntPtr streamIdsPtr, IntPtr context); /// /// A delegate that describes the requirements of the OnTrackRemovedDelegate callback. /// /// The handle to the removed source. /// Pointer to a context object. public delegate void OnTrackRemovedDelegate(ulong remoteSourceHandle, IntPtr context); /// /// A delegate that describes the requirements of the OnDataChannelReceivedDelegate callback. /// /// The handle to the newly received data channel. /// Pointer to a context object. public delegate void OnDataChannelReceivedDelegate(ulong remoteDataChanelHandle, IntPtr context); /// /// A delegate that describes the requirements of the OnSendOfferDelegate callback. /// /// The json formatted offer string. /// Pointer to a context object. public delegate void OnLocalOfferCreatedDelegate([MarshalAs(UnmanagedType.LPStr)] string offer, IntPtr context); /// /// A delegate that describes the requirements of the OnSendAnswer callback. /// /// The json formatted answer string. /// Pointer to a context object. public delegate void OnLocalAnswerCreatedDelegate([MarshalAs(UnmanagedType.LPStr)] string answer, IntPtr context); /// /// A delegate that describes the requirements of the OnSendIceCandidate callback. /// /// The ice candidate object that was sent. /// Pointer to a context object. public delegate void OnLocalIceCandidateFoundDelegate(in MLWebRTCConnectionIceCandidate iceCandidate, IntPtr context); /// /// A delegate that describes the requirements of the OnIceGatheringCompleted callback. /// /// Pointer to a context object. public delegate void OnIceGatheringCompletedDelegate(IntPtr context); private static MLResult.Code CreateRemoteConnection(MLWebRTC.IceServer[] iceServers, IntPtr proxyConfig, out PeerConnection connection) { connection = new PeerConnection(); // Converts into native ice server structs MLWebRTCConnectionIceServer[] nativeIceServers = new MLWebRTCConnectionIceServer[iceServers.Length]; for (int i = 0; i < iceServers.Length; ++i) { nativeIceServers[i].Data = iceServers[i]; } IntPtr arrayPtr = Marshal.AllocHGlobal(Marshal.SizeOf() * nativeIceServers.Length); IntPtr walkPtr = arrayPtr; for (int i = 0; i < nativeIceServers.Length; ++i) { Marshal.StructureToPtr(nativeIceServers[i], walkPtr, false); walkPtr = new IntPtr(walkPtr.ToInt64() + Marshal.SizeOf()); } NativeBindings.MLWebRTCConnectionConfig config = new NativeBindings.MLWebRTCConnectionConfig(); config.IceServers = arrayPtr; config.NumIceServers = (uint)nativeIceServers.Length; config.ProxyConfig = proxyConfig; NativeBindings.MLWebRTCConnectionEventCallbacks callbacks = new NativeBindings.MLWebRTCConnectionEventCallbacks(); callbacks.OnError = NativeBindings.OnError; callbacks.OnConnected = NativeBindings.OnConnected; callbacks.OnDisconnected = NativeBindings.OnDisconnected; callbacks.OnLocalOfferCreated = NativeBindings.OnLocalOfferCreated; callbacks.OnLocalAnswerCreated = NativeBindings.OnLocalAnswerCreated; callbacks.OnLocalIceCandidateFound = NativeBindings.OnLocalIceCandidateFound; callbacks.OnIceGatheringCompleted = NativeBindings.OnIceGatheringCompleted; callbacks.OnTrackRemoved = NativeBindings.OnTrackRemoved; callbacks.OnTrackAdded = NativeBindings.OnTrackAdded; callbacks.OnDataChannelReceived = NativeBindings.OnDataChannelReceived; connection.gcHandle = GCHandle.Alloc(connection); IntPtr gcHandlePtr = GCHandle.ToIntPtr(connection.gcHandle); callbacks.Context = gcHandlePtr; ulong connectionHandle = MagicLeapNativeBindings.InvalidHandle; MLResult.Code resultCode = NativeBindings.MLWebRTCConnectionCreate(in config, in callbacks, out connectionHandle); Marshal.FreeHGlobal(arrayPtr); if (!MLResult.IsOK(resultCode)) { connection.gcHandle.Free(); return resultCode; } connection.Handle = connectionHandle; return resultCode; } /// /// Creates a remote connection and sets up the native callbacks. /// /// The ice servers to create the connection with. /// The created connection object. /// /// MLResult.Result will be MLResult.Code.Ok if the connection was successfully created. /// 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 CreateRemoteConnection(MLWebRTC.IceServer[] iceServers, out PeerConnection connection) { return CreateRemoteConnection(iceServers, IntPtr.Zero, out connection); } /// /// Creates a remote connection with a forward proxy configuration and sets up the native callbacks. /// /// The ice servers to create the connection with. /// The forward proxy configuration. /// The created connection object. /// /// MLResult.Result will be MLResult.Code.Ok if the connection was successfully created. /// 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 CreateRemoteConnection(MLWebRTC.IceServer[] iceServers, MLWebRTC.ProxyConfig proxyConfig, out PeerConnection connection) { NativeBindings.MLWebRTCConnectionProxyConfig nativeProxyConfig = new MLWebRTCConnectionProxyConfig { Type = proxyConfig.Type, HostAddress = proxyConfig.HostAddress, HostPort = proxyConfig.HostPort, Username = proxyConfig.Username, Password = proxyConfig.Password, AutoDetect = proxyConfig.AutoDetect, AutoConfigUrl = proxyConfig.AutoConfigUrl, BypassList = proxyConfig.BypassList }; IntPtr proxyConfigPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); Marshal.StructureToPtr(nativeProxyConfig, proxyConfigPtr, false); MLResult.Code result = CreateRemoteConnection(iceServers, proxyConfigPtr, out connection); Marshal.FreeHGlobal(proxyConfigPtr); return result; } /// /// Creates a WebRTC connection. /// /// The configuration that holds the ice servers. /// The object that holds all the callbacks that will be invoked natively. /// The handle of the connection to return to the caller. /// /// MLResult.Result will be MLResult.Code.Ok if the connection was successfully created. /// 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 MLWebRTCConnectionCreate(in MLWebRTCConnectionConfig config, in MLWebRTCConnectionEventCallbacks callbacks, out ulong connectionHandle); /// /// Processes events of a connection, responsible for callbacks being invoked natively. /// /// The handle to the connection to process events from. /// /// MLResult.Result will be MLResult.Code.Ok if the connection had it's events successfully processed. /// 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 MLWebRTCConnectionProcessEvents(ulong connectionHandle); /// /// Creates an offer with a connection. /// /// The handle to the connection creating the offer. /// /// MLResult.Result will be MLResult.Code.Ok if the offer was successfully created. /// 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 MLWebRTCConnectionCreateOffer(ulong connectionHandle); /// /// Sets an offer from a remote connection. /// /// The handle to the connection responding to the offer. /// The offer string. /// /// MLResult.Result will be MLResult.Code.Ok if the offer was successfully set. /// 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 MLWebRTCConnectionSetRemoteOffer(ulong connectionHandle, [MarshalAs(UnmanagedType.LPStr)] string offer); /// /// Sets the answer to an offer from a remote connection. /// /// The handle to the connection answering to the offer. /// The json formatted answer string to set. /// /// MLResult.Result will be MLResult.Code.Ok if the answer was successfully set. /// 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 MLWebRTCConnectionSetRemoteAnswer(ulong connectionHandle, [MarshalAs(UnmanagedType.LPStr)] string answer); /// /// Adds a remote ice candidate from a connection. /// /// The handle to the connection to send the ice candidate to. /// The ice candidate to send. /// /// MLResult.Result will be MLResult.Code.Ok if the ice candidate was successfully 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 MLWebRTCConnectionAddRemoteIceCandidate(ulong connectionHandle, in MLWebRTCConnectionIceCandidate candidate); /// /// Gets if a connection is currently connected. /// /// The handle to the connection in question. /// Used to return to the user to determine if the connection is connected or not. /// /// MLResult.Result will be MLResult.Code.Ok if the connection status was successfully queried. /// 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 MLWebRTCConnectionIsConnected(ulong connectionHandle, [MarshalAs(UnmanagedType.I1)] out bool isConnected); /// /// Gets if a connection has failed /// /// The handle to the connection in question. /// Used to return to the user to determine if the connection has failed or not. /// /// MLResult.Result will be MLResult.Code.Ok if the connection status was successfully queried. /// 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 MLWebRTCConnectionHasFailed(ulong connectionHandle, [MarshalAs(UnmanagedType.I1)] out bool hasFailed); /// /// Add a local source as a media track to the connection. /// /// The handle to the connection to set the local source to. /// The handle to the local source to set onto the connection. /// Information about the track to be added /// /// MLResult.Result will be MLResult.Code.Ok if the source was successfully set on the connection. /// MLResult.Result will be MLResult.Code.UnspecifiedFailure if failed due to other internal error. /// [DllImport(MLWebRTCDLL, CallingConvention = CallingConvention.Cdecl)] public static extern MLResult.Code MLWebRTCConnectionAddLocalSourceTrackEx(ulong connectionHandle, ulong localSourceHandle, [In] ref MLWebRTCTrackInfo trackInfo); /// /// Sets the local source of a connection. /// /// The handle to the connection to set the local source to. /// The handle to the local source to set onto the connection. /// /// MLResult.Result will be MLResult.Code.Ok if the source was successfully set on the connection. /// 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 MLWebRTCConnectionRemoveLocalSourceTrack(ulong connectionHandle, ulong localSourceHandle); /// /// Destroys a WebRTC connection. /// /// The handle to the connection to destroy. /// /// MLResult.Result will be MLResult.Code.Ok if the connection was successfully destroyed. /// 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 MLWebRTCConnectionDestroy(ulong connectionHandle); /// /// Callback that is invoked when an error is made. /// /// The error message. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnErrorDelegate))] private static void OnError([MarshalAs(UnmanagedType.LPStr)] string message, IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; connection?.OnError?.Invoke(connection, message); }); } /// /// Callback that is invoked when a connection is established. /// /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnConnectedDelegate))] private static void OnConnected(IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; connection?.OnConnected?.Invoke(connection); }); } /// /// Callback that is for OnDisconnected. /// /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnDisconnectedDelegate))] private static void OnDisconnected(IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; connection?.OnDisconnected?.Invoke(connection); }); } /// /// Callback that is for OnTrackAdded. /// /// The native source object that was added. /// The number of streams this track belongs to. /// The names of the streams this track belongs to. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnTrackAddedDelegate))] private static void OnTrackAdded(in MLWebRTC.Source.NativeBindings.MLWebRTCSource sourceNative, uint numStreamIds, IntPtr streamIdsPtr, IntPtr context) { if (sourceNative.Handle == MagicLeapNativeBindings.InvalidHandle) { Debug.LogError("Received invalid source handle in NativeBindings.OnTrackAdded() callback"); return; } MLWebRTC.MediaStream.Track addedTrack = sourceNative.Data; const string defaultStreamId = "unknown_remote"; int ptrSize = Marshal.SizeOf(typeof(IntPtr)); // TODO : marshal directly as a string array instead of IntPtr. string[] streamIds = new string[numStreamIds]; for (int i = 0; i < numStreamIds; ++i) { IntPtr ptr = Marshal.ReadIntPtr(streamIdsPtr, i * ptrSize); streamIds[i] = (ptr != IntPtr.Zero) ? Marshal.PtrToStringAnsi(ptr) : defaultStreamId; } if (streamIds.Length == 0) { streamIds = new string[1]; streamIds[0] = defaultStreamId; } MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; addedTrack.ParentConnection = connection; List mediaStreams = new List(); foreach (string streamId in streamIds) { MLWebRTC.MediaStream mediaStream = null; if (!connection.remoteMediaStreams.ContainsKey(streamId)) { mediaStream = MLWebRTC.MediaStream.Create(connection, streamId); connection.remoteMediaStreams.Add(mediaStream.Id, mediaStream); } else { mediaStream = connection.remoteMediaStreams[streamId]; } mediaStream.Tracks.Add(addedTrack); // Mark this track as "selected" if this is the first track of its type in this stream. if (addedTrack.TrackType == MediaStream.Track.Type.Audio && mediaStream.ActiveAudioTrack == null) { mediaStream.SelectTrack(addedTrack); } else if (addedTrack.TrackType == MediaStream.Track.Type.Video && mediaStream.ActiveVideoTrack == null) { mediaStream.SelectTrack(addedTrack); } mediaStreams.Add(mediaStream); } connection.OnTrackAddedMultipleStreams?.Invoke(mediaStreams, addedTrack); }); } /// /// Callback that is for OnTrackRemoved. /// /// The handle to the removed source. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnTrackRemovedDelegate))] private static void OnTrackRemoved(ulong remoteSourceHandle, IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; if (!MagicLeapNativeBindings.MLHandleIsValid(remoteSourceHandle) || connection == null) { return; } List mediaStreams = new List(); MLWebRTC.MediaStream.Track removedTrack = null; foreach (MLWebRTC.MediaStream remoteMediaStream in connection.remoteMediaStreams.Values) { foreach (MLWebRTC.MediaStream.Track track in remoteMediaStream.Tracks) { if (track.Handle == remoteSourceHandle) { removedTrack = track; remoteMediaStream.UnSelectTrack(removedTrack); remoteMediaStream.Tracks.Remove(removedTrack); mediaStreams.Add(remoteMediaStream); break; } } } removedTrack.Cleanup(); connection.OnTrackRemovedMultipleStreams?.Invoke(mediaStreams, removedTrack); }); } /// /// Callback that is for OnDataChannelReceived. /// /// The handle to the newly received data channel. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnDataChannelReceivedDelegate))] private static void OnDataChannelReceived(ulong dataChannelHandle, IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; if (connection == null) { return; } if (!MagicLeapNativeBindings.MLHandleIsValid(dataChannelHandle)) { return; } MLWebRTC.DataChannel dataChannel = new MLWebRTC.DataChannel(dataChannelHandle) { ParentConnection = connection }; MLResult.Code resultCode = MLWebRTC.DataChannel.NativeBindings.SetCallbacks(dataChannel); if (!MLResult.IsOK(resultCode)) { return; } IntPtr labelPtr = IntPtr.Zero; resultCode = MLWebRTC.DataChannel.NativeBindings.MLWebRTCDataChannelGetLabel(dataChannelHandle, out labelPtr); if (!MLResult.IsOK(resultCode)) { return; } if (labelPtr != IntPtr.Zero) { dataChannel.Label = Marshal.PtrToStringAnsi(labelPtr); } resultCode = MLWebRTC.DataChannel.NativeBindings.MLWebRTCDataChannelReleaseLabelMemory(dataChannelHandle, labelPtr); connection.remoteDataChannels.Add(dataChannel); connection.OnDataChannelReceived?.Invoke(connection, dataChannel); }); } /// /// Callback that is invoked when an offer is sent. /// /// The sdp string that was sent. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnLocalOfferCreatedDelegate))] private static void OnLocalOfferCreated([MarshalAs(UnmanagedType.LPStr)] string sendSdp, IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; connection?.OnLocalOfferCreated?.Invoke(connection, sendSdp); }); } /// /// Callback that is invoked when an answer is sent. /// /// The answer string that was sent. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnLocalAnswerCreatedDelegate))] private static void OnLocalAnswerCreated([MarshalAs(UnmanagedType.LPStr)] string sendAnswer, IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; connection?.OnLocalAnswerCreated?.Invoke(connection, sendAnswer); }); } /// /// Callback that is invoked when an ice candidate is sent. /// /// The native ice candidate object that was sent. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnLocalIceCandidateFoundDelegate))] private static void OnLocalIceCandidateFound(in NativeBindings.MLWebRTCConnectionIceCandidate nativeIceCandidate, IntPtr context) { MLWebRTC.IceCandidate iceCandidate = nativeIceCandidate.Data; MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; connection?.OnLocalIceCandidateFound?.Invoke(connection, iceCandidate); }); } /// /// Callback that is invoked when an ice candidate is sent. /// /// The native ice candidate object that was sent. /// Pointer to a context object. [AOT.MonoPInvokeCallback(typeof(NativeBindings.OnIceGatheringCompletedDelegate))] private static void OnIceGatheringCompleted(IntPtr context) { MLThreadDispatch.ScheduleMain(() => { GCHandle gcHandle = GCHandle.FromIntPtr(context); PeerConnection connection = gcHandle.Target as PeerConnection; connection?.OnIceGatheringCompleted?.Invoke(connection); }); } /// /// The native representation of the MLWebRTC connection configuration. /// [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCConnectionConfig { /// /// Version of the struct. /// public uint Version; /// /// Pointer to the array of ice servers. /// public IntPtr IceServers; /// /// Number of ice servers the above pointer can step through. /// public uint NumIceServers; /// /// (Optional) Config for a forward proxy /// public IntPtr ProxyConfig; } /// /// The native representation of the MLWebRTC callback events. /// [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCConnectionEventCallbacks { /// /// Version of the struct. /// public uint Version; /// /// Context pointer. /// public IntPtr Context; /// /// OnError event. /// public OnErrorDelegate OnError; /// /// OnConnected event. /// public OnConnectedDelegate OnConnected; /// /// OnDisconnected event. /// public OnDisconnectedDelegate OnDisconnected; /// /// OnTrackAdded event. /// public OnTrackAddedDelegate OnTrackAdded; /// /// OnTrackRemoved event. /// public OnTrackRemovedDelegate OnTrackRemoved; /// /// OnDataChannelReceived event. /// public OnDataChannelReceivedDelegate OnDataChannelReceived; /// /// OnSendOffer event. /// public OnLocalOfferCreatedDelegate OnLocalOfferCreated; /// /// OnSendAnswer event. /// public OnLocalAnswerCreatedDelegate OnLocalAnswerCreated; /// /// OnSendIceCandidate event. /// public OnLocalIceCandidateFoundDelegate OnLocalIceCandidateFound; /// /// OnIceGatheringCompleted event. /// public OnIceGatheringCompletedDelegate OnIceGatheringCompleted; } /// /// The native representation of an ice server. /// [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCConnectionIceServer { /// /// Version of the struct. /// public uint Version; /// /// Uri of the ice server. /// [MarshalAs(UnmanagedType.LPStr)] public string Uri; /// /// Username of the ice server. /// [MarshalAs(UnmanagedType.LPStr)] public string UserName; /// /// Password to the ice server. /// [MarshalAs(UnmanagedType.LPStr)] public string Password; /// /// Sets the data of this object from another MLWebRTC.IceServer object. /// public MLWebRTC.IceServer Data { set { this.Uri = value.Uri; this.UserName = value.UserName; this.Password = value.Password; } } } /// /// The native representation of an ice candidate. /// [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCConnectionIceCandidate { /// /// Version of the struct. /// public uint Version; /// /// The id of the ice candidate. /// [MarshalAs(UnmanagedType.LPStr)] public string Candidate; /// /// The sdp mid of the ice candidate. /// [MarshalAs(UnmanagedType.LPStr)] public string SdpMid; /// /// The sdp mline index of the ice candidate. /// public int SdpMlineIndex; /// /// Gets or sets data from an MLWebRTC.IceCandidate object. /// public MLWebRTC.IceCandidate Data { get { return MLWebRTC.IceCandidate.Create(this.Candidate, this.SdpMid, this.SdpMlineIndex); } set { this.Candidate = value.Candidate; this.SdpMid = value.SdpMid; this.SdpMlineIndex = value.SdpMLineIndex; } } } /// /// Native representation of proxy config /// [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCConnectionProxyConfig { /// /// Type of proxy /// public MLWebRTC.ProxyType Type; /// /// Proxy server address /// [MarshalAs(UnmanagedType.LPStr)] public string HostAddress; /// /// Proxy server port /// public int HostPort; /// /// Proxy server auth username /// [MarshalAs(UnmanagedType.LPStr)] public string Username; /// /// Proxy server auth password /// [MarshalAs(UnmanagedType.LPStr)] public string Password; /// /// Attempt to automatically detect proxy /// [MarshalAs(UnmanagedType.I1)] public bool AutoDetect; /// /// Url to use to download the proxy config /// [MarshalAs(UnmanagedType.LPStr)] public string AutoConfigUrl; /// /// List of urls that should bypass the proxy /// [MarshalAs(UnmanagedType.LPStr)] public string BypassList; } [StructLayout(LayoutKind.Sequential)] public struct MLWebRTCTrackInfo { /// /// Struct version /// private readonly uint version; /// /// The names of each stream this track belongs to /// private readonly uint streamCount; /// /// Names of the streams this track belongs to. /// [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] private IntPtr[] streams; /// /// Id of the track. /// [MarshalAs(UnmanagedType.LPStr)] private readonly string trackId; public MLWebRTCTrackInfo(string[] streams, string trackId) { this.version = 1; this.streamCount = (streams != null) ? (uint)streams.Length : 0; this.trackId = trackId ?? string.Empty; // [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr)] doesnt work when char** is in a struct instead of a func param. this.streams = (streams != null) ? new IntPtr[streams.Length] : null; for (int i = 0; i < streamCount; ++i) { this.streams[i] = Marshal.StringToHGlobalAnsi(streams[i]); } } public void FreeUnmanagedMemory() { foreach (IntPtr ptr in streams) { Marshal.FreeHGlobal(ptr); } streams = null; } } } } } }