using System; using System.Drawing; using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; namespace IPCamAppBar { public class FrameEventArgs : EventArgs { public Image Image { get; set; } } public class StreamExceptionEventArgs : EventArgs { public Exception Exception { get; set; } } public delegate void FrameEvent(object sender, FrameEventArgs args); public delegate void StreamExceptionEvent(object sender, StreamExceptionEventArgs args); internal abstract class CameraStream : IDisposable { public event FrameEvent Frame; public event StreamExceptionEvent StreamException; private readonly CancellationTokenSource cancelTaskTokenSource = new CancellationTokenSource(); private Task streamTask; private DataMonitor dataMonitor; protected CameraStream() { } public void Start(string url) { if (streamTask != null) throw new InvalidOperationException("CameraStream already started"); streamTask = Task.Run(() => Fetch(url, cancelTaskTokenSource.Token)); } public void Dispose() { cancelTaskTokenSource.Cancel(); try { streamTask?.Wait(); } catch (AggregateException e) { if (e.InnerExceptions.Count == 1 && e.InnerExceptions[0] is TaskCanceledException) return; throw; } catch (TaskCanceledException) { } } private async Task Fetch(string url, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { var uri = new Uri(url); var request = WebRequest.CreateHttp(uri); if (!string.IsNullOrEmpty(uri.UserInfo)) { var parts = uri.UserInfo.Split(':'); request.Credentials = new NetworkCredential(parts[0], parts.Length > 1 ? parts[1] : ""); } request.ReadWriteTimeout = 10; try { HttpWebResponse response; using (cancellationToken.Register(() => request.Abort(), false)) { response = (HttpWebResponse)await request.GetResponseAsync(); cancellationToken.ThrowIfCancellationRequested(); } if (response.StatusCode != HttpStatusCode.OK) throw new WebException(response.StatusDescription); using (var responseStream = response.GetResponseStream()) { var dataMonitorCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); dataMonitor = new DataMonitor(dataMonitorCancellationTokenSource, TimeSpan.FromSeconds(15)); try { await ReadFrames(responseStream, dataMonitorCancellationTokenSource.Token); } catch (TaskCanceledException) { if (!dataMonitorCancellationTokenSource.IsCancellationRequested) throw; } } } catch (TaskCanceledException) { } catch (Exception e) { if (cancellationToken.IsCancellationRequested) break; OnStreamException(new StreamExceptionEventArgs { Exception = e }); await Task.Delay(5000, cancellationToken); } } } protected abstract Task ReadFrames(Stream stream, CancellationToken cancellationToken); protected virtual void OnFrame(FrameEventArgs args) { dataMonitor.Reset(); Frame?.Invoke(this, args); } protected virtual void OnStreamException(StreamExceptionEventArgs args) { StreamException?.Invoke(this, args); } } internal static class BufferExtensions { public static int Find(this byte[] buffer, byte[] pattern, int limit = int.MaxValue, int startAt = 0) { var patternIndex = 0; var bufferIndex = 0; for (bufferIndex = startAt; bufferIndex < buffer.Length && patternIndex < pattern.Length && bufferIndex < limit; bufferIndex++) { if (buffer[bufferIndex] == pattern[patternIndex]) { patternIndex++; } else { patternIndex = 0; } } if (patternIndex == pattern.Length) return bufferIndex - pattern.Length; return -1; } } internal class DataMonitor { private readonly CancellationTokenSource cancellationTokenSource; private readonly long timeout; private readonly Timer timeoutTimer; public DataMonitor(CancellationTokenSource cancellationTokenSource, TimeSpan timeout) { this.cancellationTokenSource = cancellationTokenSource; this.timeout = (long)timeout.TotalMilliseconds; timeoutTimer = new Timer(Tick, null, this.timeout, -1); } public void Reset() { timeoutTimer.Change(timeout, -1); } private void Tick(object state) { cancellationTokenSource.Cancel(); } } }