diff --git a/AgroBase/OperationControl/Services/TCPVideoReceiver.cs b/AgroBase/OperationControl/Services/TCPVideoReceiver.cs new file mode 100644 index 000000000..99f4b84ac --- /dev/null +++ b/AgroBase/OperationControl/Services/TCPVideoReceiver.cs @@ -0,0 +1,156 @@ +using ScottPlot.Colormaps; +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Imaging; + +namespace OperationControl.Services +{ + internal class TCPVideoReceiver + { + private readonly int _port; + private readonly System.Windows.Controls.Image _imageControl; + + private volatile bool _rodando; + private Thread _thread; + private TcpListener _listener; + + public TCPVideoReceiver(int port, System.Windows.Controls.Image imageControl) + { + _port = port; + _imageControl = imageControl; + } + + public void Iniciar() + { + if (_rodando) return; + + _rodando = true; + _thread = new Thread(Run) { IsBackground = true }; + _thread.Start(); + } + + public void Parar() + { + _rodando = false; + try + { + _listener?.Stop(); + _imageControl.Source = null; + } + catch { /* ignora */ } + } + + private void Run() + { + try + { + _listener = new TcpListener(IPAddress.Any, _port); + _listener.Start(); + Console.WriteLine($"[VideoReceiver] Ouvindo na porta {_port}..."); + + while (_rodando) + { + TcpClient client = null; + + try + { + // Em vez de bloquear direto no Accept, usamos Pending() + Sleep + if (!_listener.Pending()) + { + Thread.Sleep(100); + continue; + } + + client = _listener.AcceptTcpClient(); + client.NoDelay = true; + Console.WriteLine("[VideoReceiver] Cliente conectado."); + + using (var stream = client.GetStream()) + using (var br = new BinaryReader(stream)) + { + while (_rodando && client.Connected) + { + // 1) lê tamanho (4 bytes big-endian) + byte[] sizeBytes = br.ReadBytes(4); + if (sizeBytes.Length < 4) + break; + + int size = + (sizeBytes[0] << 24) | + (sizeBytes[1] << 16) | + (sizeBytes[2] << 8) | + (sizeBytes[3]); + + // 2) lê JPEG + byte[] imgBytes = br.ReadBytes(size); + if (imgBytes.Length < size) + break; + + _imageControl.Dispatcher.BeginInvoke(new Action(() => + { + try + { + using (var ms = new MemoryStream(imgBytes)) + { + var bmp = new BitmapImage(); + bmp.BeginInit(); + bmp.CacheOption = BitmapCacheOption.OnLoad; + bmp.StreamSource = ms; + bmp.EndInit(); + bmp.Freeze(); + _imageControl.Source = bmp; + } + } + catch + { + // ignora frame corrompido ou problema de decode + } + })); + } + } + + Console.WriteLine("[VideoReceiver] Cliente desconectado."); + client.Close(); + } + catch (SocketException ex) + { + // WSACancelBlockingCall ao parar o listener é normal + if (!_rodando) + break; + + Console.WriteLine("[VideoReceiver] SocketException: " + ex.Message); + client?.Close(); + Thread.Sleep(500); + } + catch (IOException) + { + // desconexão abrupta do cliente + client?.Close(); + Thread.Sleep(200); + } + catch (Exception ex) + { + Console.WriteLine("[VideoReceiver] Erro geral: " + ex.Message); + client?.Close(); + Thread.Sleep(500); + } + } + + Console.WriteLine("[VideoReceiver] Loop encerrado."); + } + catch (Exception ex) + { + Console.WriteLine("[VideoReceiver] Erro ao iniciar listener: " + ex.Message); + } + finally + { + try { _listener?.Stop(); } catch { } + } + } + } +}