aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.WebRTC
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2020-03-07 13:40:48 +0200
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2020-03-07 13:40:48 +0200
commit9d18d9ec9658af3716814625b608d58ad3daa14c (patch)
treeb5b586e6eaf7c9d4df4cbac120e4c6b4c01b1265 /Software/Visual_Studio/Tango.WebRTC
parent574f075256fbadd2dadb769c5aff5ebfd6dedd56 (diff)
downloadTango-9d18d9ec9658af3716814625b608d58ad3daa14c.tar.gz
Tango-9d18d9ec9658af3716814625b608d58ad3daa14c.zip
Added Tango.WebRTC
Diffstat (limited to 'Software/Visual_Studio/Tango.WebRTC')
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/Answer.cs13
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/DataMessageReceivedEventArgs.cs13
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/ErrorEventArgs.cs13
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/IceCandidate.cs26
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/NewIceCandidateEventArgs.cs13
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/Offer.cs13
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/Properties/AssemblyInfo.cs36
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj64
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/VideoFrameReceivedEventArgs.cs14
-rw-r--r--Software/Visual_Studio/Tango.WebRTC/WebRtcClient.cs537
10 files changed, 742 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.WebRTC/Answer.cs b/Software/Visual_Studio/Tango.WebRTC/Answer.cs
new file mode 100644
index 000000000..535162dca
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/Answer.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.WebRTC
+{
+ public class Answer
+ {
+ public String Sdp { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.WebRTC/DataMessageReceivedEventArgs.cs b/Software/Visual_Studio/Tango.WebRTC/DataMessageReceivedEventArgs.cs
new file mode 100644
index 000000000..6faa14ccb
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/DataMessageReceivedEventArgs.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.WebRTC
+{
+ public class DataMessageReceivedEventArgs<T> : EventArgs
+ {
+ public T Data { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.WebRTC/ErrorEventArgs.cs b/Software/Visual_Studio/Tango.WebRTC/ErrorEventArgs.cs
new file mode 100644
index 000000000..658468bf4
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/ErrorEventArgs.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.WebRTC
+{
+ public class ErrorEventArgs : EventArgs
+ {
+ public String Error { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.WebRTC/IceCandidate.cs b/Software/Visual_Studio/Tango.WebRTC/IceCandidate.cs
new file mode 100644
index 000000000..af9aa19b5
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/IceCandidate.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.WebRTC
+{
+ public class IceCandidate
+ {
+ /// <summary>
+ /// Gets or sets the SDP mid string.
+ /// </summary>
+ public String SdpMid { get; set; }
+
+ /// <summary>
+ /// Gets or sets the SDP line index string.
+ /// </summary>
+ public int SdpMLineIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the candidate string.
+ /// </summary>
+ public String Sdp { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.WebRTC/NewIceCandidateEventArgs.cs b/Software/Visual_Studio/Tango.WebRTC/NewIceCandidateEventArgs.cs
new file mode 100644
index 000000000..5ec1591f9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/NewIceCandidateEventArgs.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.WebRTC
+{
+ public class NewIceCandidateEventArgs : EventArgs
+ {
+ public IceCandidate IceCandidate { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.WebRTC/Offer.cs b/Software/Visual_Studio/Tango.WebRTC/Offer.cs
new file mode 100644
index 000000000..7e8f736c2
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/Offer.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.WebRTC
+{
+ public class Offer
+ {
+ public String Sdp { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.WebRTC/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.WebRTC/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..b4b3f9476
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Tango.WebRTC")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Tango.WebRTC")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("09f81a12-0f77-4336-854d-9e0a74a17f9e")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj b/Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj
new file mode 100644
index 000000000..0d85bb902
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/Tango.WebRTC.csproj
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{09F81A12-0F77-4336-854D-9E0A74A17F9E}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tango.WebRTC</RootNamespace>
+ <AssemblyName>Tango.WebRTC</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Answer.cs" />
+ <Compile Include="DataMessageReceivedEventArgs.cs" />
+ <Compile Include="ErrorEventArgs.cs" />
+ <Compile Include="IceCandidate.cs" />
+ <Compile Include="NewIceCandidateEventArgs.cs" />
+ <Compile Include="Offer.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="VideoFrameReceivedEventArgs.cs" />
+ <Compile Include="WebRtcClient.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\SideChains\WebRtc.NET\WebRtc.NET.vcxproj">
+ <Project>{a07e6cb4-0132-4eb1-9a38-c8c057884dc2}</Project>
+ <Name>WebRtc.NET</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.WebRTC/VideoFrameReceivedEventArgs.cs b/Software/Visual_Studio/Tango.WebRTC/VideoFrameReceivedEventArgs.cs
new file mode 100644
index 000000000..d147b89d4
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/VideoFrameReceivedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.WebRTC
+{
+ public class VideoFrameReceivedEventArgs : EventArgs
+ {
+ public Bitmap Bitmap { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.WebRTC/WebRtcClient.cs b/Software/Visual_Studio/Tango.WebRTC/WebRtcClient.cs
new file mode 100644
index 000000000..9866395ae
--- /dev/null
+++ b/Software/Visual_Studio/Tango.WebRTC/WebRtcClient.cs
@@ -0,0 +1,537 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WebRtc.NET;
+
+namespace Tango.WebRTC
+{
+ public class WebRtcClient : IDisposable
+ {
+ private ManagedConductor _conductor;
+ private Thread _conductorThread;
+ private TurboJpegEncoder _encoder;
+ private byte[] _bgrBufflocal;
+ private byte[] _imgBuf;
+ private GCHandle _bufHandle;
+ private IntPtr _imgBufPtr = IntPtr.Zero;
+ private bool _isDisposed;
+ private Bitmap _sendFrame;
+ private TaskCompletionSource<bool> _readyCompletionSource;
+
+ #region Events
+
+ public event EventHandler<NewIceCandidateEventArgs> NewIceCandidate;
+ public event EventHandler Ready;
+ public event EventHandler<DataMessageReceivedEventArgs<String>> TextMessageReceived;
+ public event EventHandler<DataMessageReceivedEventArgs<byte[]>> BinaryMessageReceived;
+ public event EventHandler<VideoFrameReceivedEventArgs> FrameReceived;
+ public event EventHandler<ErrorEventArgs> Error;
+ public event EventHandler Disconnected;
+
+ #endregion
+
+ #region Properties
+
+ private int _frameWidth;
+ public int FrameWidth
+ {
+ get { return _frameWidth; }
+ set
+ {
+ if (IsInitialized)
+ {
+ throw new InvalidOperationException("The frame height must be set before calling Init();");
+ }
+
+ _frameWidth = value;
+ }
+ }
+
+ private int _frameHeight;
+ public int FrameHeight
+ {
+ get { return _frameHeight; }
+ set
+ {
+ if (IsInitialized)
+ {
+ throw new InvalidOperationException("The frame width must be set before calling Init();");
+ }
+
+ _frameHeight = value;
+ }
+ }
+
+ private int _frameRate;
+ public int FrameRate
+ {
+ get { return _frameRate; }
+ set
+ {
+ if (IsInitialized)
+ {
+ throw new InvalidOperationException("The frame rate must be set before calling Init();");
+ }
+
+ _frameRate = value;
+ }
+ }
+
+
+ private String _dataChannelName;
+ public String DataChannelName
+ {
+ get { return _dataChannelName; }
+ set
+ {
+ if (IsInitialized)
+ {
+ throw new InvalidOperationException("The data channel must be set before calling Init();");
+ }
+
+ _dataChannelName = value;
+ }
+ }
+
+ public bool IsInitialized { get; private set; }
+ public bool IsReady { get; private set; }
+
+ #endregion
+
+ #region Constructors
+
+ public WebRtcClient()
+ {
+ FrameWidth = 640;
+ FrameHeight = 480;
+ FrameRate = 5;
+ DataChannelName = "DefaultChannelName";
+ }
+
+ public WebRtcClient(int frameWidth, int frameHeight, int frameRate) : this()
+ {
+ FrameWidth = frameWidth;
+ FrameHeight = frameHeight;
+ FrameRate = frameRate;
+ }
+
+ public WebRtcClient(int frameWidth, int frameHeight, int frameRate, String dataChannelName) : this(frameWidth, frameHeight, frameRate)
+ {
+ DataChannelName = dataChannelName;
+ }
+
+ #endregion
+
+ #region Init
+
+ public Task Init()
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException("This instance was already disposed.");
+ }
+
+ if (IsInitialized)
+ {
+ throw new InvalidOperationException("This instance was already initialized.");
+ }
+
+ TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
+
+ _conductorThread = new Thread(() =>
+ {
+ Thread.Sleep(5); //Wait for function to return at least!
+
+ _conductor = new ManagedConductor();
+ _encoder = TurboJpegEncoder.CreateEncoder();
+
+ try
+ {
+ ManagedConductor.InitializeSSL();
+
+ _conductor.AddServerConfig("stun:stun.l.google.com:19302", String.Empty, String.Empty);
+ _conductor.AddServerConfig("stun:stun.anyfirewall.com:3478", String.Empty, String.Empty);
+ _conductor.AddServerConfig("stun:stun.stunprotocol.org:3478", String.Empty, String.Empty);
+
+ _conductor.SetAudio(false);
+ _conductor.SetVideoCapturer(FrameWidth, FrameHeight, FrameRate, false);
+
+ if (!_conductor.InitializePeerConnection())
+ {
+ completion.SetException(new ApplicationException("Error initializing peer connection."));
+ }
+
+ _conductor.CreateDataChannel(DataChannelName);
+ _conductor.OnIceCandidate += _conductor_OnIceCandidate;
+ _conductor.OnDataMessage += _conductor_OnDataMessage;
+ _conductor.OnDataBinaryMessage += _conductor_OnDataBinaryMessage;
+ _conductor.OnError += _conductor_OnError;
+ _conductor.OnFailure += _conductor_OnFailure;
+ _conductor.OnIceStateChanged += _conductor_OnIceStateChanged;
+
+ unsafe
+ {
+ _conductor.OnRenderRemote += _conductor_OnRenderRemote;
+ }
+
+ _conductor.ProcessMessages(1000);
+ }
+ catch (Exception ex)
+ {
+ completion.SetException(ex);
+ }
+
+ IsInitialized = true;
+
+ completion.SetResult(true);
+
+ Stopwatch watch = new Stopwatch();
+ watch.Start();
+
+ while (!_isDisposed)
+ {
+ watch.Restart();
+ _conductor.ProcessMessages(1000);
+ Thread.Sleep(Math.Max(10, (int)watch.ElapsedMilliseconds));
+ }
+
+ IsInitialized = false;
+
+ _conductor.OnIceCandidate -= _conductor_OnIceCandidate;
+ _conductor.OnDataMessage -= _conductor_OnDataMessage;
+ _conductor.OnDataBinaryMessage -= _conductor_OnDataBinaryMessage;
+ _conductor.OnError -= _conductor_OnError;
+ _conductor.OnFailure -= _conductor_OnFailure;
+ _conductor.OnIceStateChanged -= _conductor_OnIceStateChanged;
+
+ _conductor.Dispose();
+ });
+
+ _conductorThread.IsBackground = true;
+ _conductorThread.Start();
+
+ return completion.Task;
+ }
+
+ private void _conductor_OnIceStateChanged(IceConnectionStates state)
+ {
+ if (!_isDisposed)
+ {
+ if (state == IceConnectionStates.kIceConnectionConnected)
+ {
+ OnReady();
+ }
+ else if (state == IceConnectionStates.kIceConnectionFailed)
+ {
+ Disconnected?.Invoke(this, new EventArgs());
+ }
+ }
+ }
+
+ #endregion
+
+ #region WebRTC Event Handlers
+
+ private void _conductor_OnIceCandidate(string sdp_mid, int sdp_mline_index, string sdp)
+ {
+ NewIceCandidate?.Invoke(this, new NewIceCandidateEventArgs()
+ {
+ IceCandidate = new IceCandidate()
+ {
+ SdpMid = sdp_mid,
+ SdpMLineIndex = sdp_mline_index,
+ Sdp = sdp
+ }
+ });
+ }
+
+ private void _conductor_OnDataMessage(string text)
+ {
+ OnTextMessageReceived(text);
+ }
+
+ private void _conductor_OnDataBinaryMessage(byte[] data)
+ {
+ OnBinaryMessageReceived(data);
+ }
+
+ unsafe private void _conductor_OnRenderRemote(byte* frame_buffer, uint w, uint h)
+ {
+ if (_isDisposed) return;
+
+ try
+ {
+ if (_encoder.EncodeI420toBGR24(frame_buffer, w, h, ref _bgrBufflocal, true) == 0)
+ {
+ var bufHandle = GCHandle.Alloc(_bgrBufflocal, GCHandleType.Pinned);
+ var bmp = new Bitmap((int)w, (int)h, (int)w * 3, PixelFormat.Format24bppRgb, bufHandle.AddrOfPinnedObject());
+ FrameReceived?.Invoke(this, new VideoFrameReceivedEventArgs()
+ {
+ Bitmap = bmp
+ });
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Error occurred while receiving the remote video frame.\n{ex.Message}");
+ }
+ }
+
+ private void _conductor_OnFailure(string error)
+ {
+ OnError(error);
+ }
+
+ private void _conductor_OnError()
+ {
+ OnError("Unspecified error.");
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public Task<Offer> CreateOffer()
+ {
+ EnsureInitialized();
+
+ TaskCompletionSource<Offer> completion = new TaskCompletionSource<Offer>();
+
+ ManagedConductor.OnCallbackSdp del = null;
+
+ bool completed = false;
+
+ del = (sdp) =>
+ {
+ if (!completed)
+ {
+ completed = true;
+ _conductor.OnSuccessOffer -= del;
+ completion.SetResult(new Offer() { Sdp = sdp });
+ }
+ };
+
+ _conductor.OnSuccessOffer += del;
+
+ Task.Factory.StartNew(async () =>
+ {
+ await Task.Delay(10000);
+
+ if (!completed)
+ {
+ completed = true;
+ completion.SetException(new TimeoutException("The offer was not created within the given time."));
+ }
+ });
+
+ _conductor.CreateOffer();
+
+ return completion.Task;
+ }
+
+ public Task<Answer> CreateAnswer(Offer offer)
+ {
+ EnsureInitialized();
+
+ TaskCompletionSource<Answer> completion = new TaskCompletionSource<Answer>();
+
+ ManagedConductor.OnCallbackSdp del = null;
+
+ bool completed = false;
+
+ del = (sdp) =>
+ {
+ if (!completed)
+ {
+ completed = true;
+ _conductor.OnSuccessAnswer -= del;
+ completion.SetResult(new Answer() { Sdp = sdp });
+ }
+ };
+
+ _conductor.OnSuccessAnswer += del;
+
+ Task.Factory.StartNew(async () =>
+ {
+ await Task.Delay(10000);
+
+ if (!completed)
+ {
+ completed = true;
+ completion.SetException(new TimeoutException("The answer was not created within the given time."));
+ }
+ });
+
+ _conductor.OnOfferRequest(offer.Sdp);
+
+ return completion.Task;
+ }
+
+ public void SetAnswer(Answer answer)
+ {
+ EnsureInitialized();
+
+ _conductor.OnOfferReply("answer", answer.Sdp);
+ }
+
+ public void SendText(String msg)
+ {
+ EnsureInitialized();
+
+ _conductor.DataChannelSendText(msg);
+ }
+
+ public void AddIceCandidate(IceCandidate ice)
+ {
+ EnsureInitialized();
+
+ Task.Factory.StartNew(() =>
+ {
+ _conductor.AddIceCandidate(ice.SdpMid, ice.SdpMLineIndex, ice.Sdp);
+ });
+ }
+
+ public unsafe void PushFrame(Bitmap bitmap)
+ {
+ if (_isDisposed) return;
+
+ EnsureInitialized();
+
+ try
+ {
+ if (_sendFrame == null)
+ {
+ _imgBuf = new byte[FrameWidth * 3 * FrameHeight];
+ _bufHandle = GCHandle.Alloc(_imgBuf, GCHandleType.Pinned);
+ _imgBufPtr = _bufHandle.AddrOfPinnedObject();
+ _sendFrame = new Bitmap(FrameWidth, FrameHeight, FrameWidth * 3, PixelFormat.Format24bppRgb, _imgBufPtr);
+ }
+
+ using (var g = Graphics.FromImage(_sendFrame))
+ {
+ g.DrawImage(bitmap, new Rectangle(0, 0, _sendFrame.Width, _sendFrame.Height));
+ }
+
+ byte* firstYuv = _conductor.VideoCapturerI420Buffer();
+
+ int yuvSize = _encoder.EncodeI420((byte*)_imgBufPtr.ToPointer(), FrameWidth, FrameHeight, (int)TJPF.TJPF_BGR, 0, true, firstYuv);
+
+ _conductor.PushFrame();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Error occurred while pushing the frame.\n{ex.Message}");
+ }
+ }
+
+ public Task WaitForReady(TimeSpan? timeout = null)
+ {
+ if (!IsReady)
+ {
+ if (timeout != null)
+ {
+ Task.Factory.StartNew(async () =>
+ {
+ await Task.Delay(timeout.Value);
+
+ if (!IsReady)
+ {
+ _readyCompletionSource.SetException(new TimeoutException("The connection was not ready within the given time."));
+ _readyCompletionSource = null;
+ }
+ });
+ }
+
+ _readyCompletionSource = new TaskCompletionSource<bool>();
+ return _readyCompletionSource.Task;
+ }
+ else
+ {
+ return Task.FromResult(true);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+
+ try
+ {
+ if (_sendFrame != null)
+ {
+ _sendFrame.Dispose();
+ }
+
+ }
+ catch { }
+
+
+ try
+ {
+ if (_bufHandle != null)
+ {
+ _bufHandle.Free();
+ }
+ }
+ catch { }
+ }
+ }
+
+ #endregion
+
+ #region Virtual Methods
+
+ protected virtual void OnTextMessageReceived(String text)
+ {
+ TextMessageReceived?.Invoke(this, new DataMessageReceivedEventArgs<string>() { Data = text });
+ }
+
+ protected virtual void OnBinaryMessageReceived(byte[] data)
+ {
+ BinaryMessageReceived?.Invoke(this, new DataMessageReceivedEventArgs<byte[]>() { Data = data });
+ }
+
+ protected virtual void OnReady()
+ {
+ if (!IsReady)
+ {
+ IsReady = true;
+ Ready?.Invoke(this, new EventArgs());
+
+ if (_readyCompletionSource != null)
+ {
+ _readyCompletionSource.SetResult(true);
+ _readyCompletionSource = null;
+ }
+ }
+ }
+
+ protected virtual void OnError(String error)
+ {
+ Error?.Invoke(this, new ErrorEventArgs() { Error = error });
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void EnsureInitialized()
+ {
+ if (!IsInitialized)
+ {
+ throw new InvalidOperationException("Invalid operation. The instance was not initialized using Init();");
+ }
+ }
+
+ #endregion
+ }
+}