IPCamAppBar/IPCamAppBar/CameraStream.cs

204 lines
5.8 KiB
C#

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();
}
}
}