2019-08-23 09:13:10 +00:00
|
|
|
|
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; }
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-24 15:35:41 +00:00
|
|
|
|
public class StreamExceptionEventArgs : EventArgs
|
|
|
|
|
{
|
|
|
|
|
public Exception Exception { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-08-23 09:13:10 +00:00
|
|
|
|
public delegate void FrameEvent(object sender, FrameEventArgs args);
|
2019-08-24 15:35:41 +00:00
|
|
|
|
public delegate void StreamExceptionEvent(object sender, StreamExceptionEventArgs args);
|
2019-08-23 09:13:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal abstract class CameraStream : IDisposable
|
|
|
|
|
{
|
|
|
|
|
public event FrameEvent Frame;
|
2019-08-24 15:35:41 +00:00
|
|
|
|
public event StreamExceptionEvent StreamException;
|
2019-08-23 09:13:10 +00:00
|
|
|
|
|
|
|
|
|
private readonly CancellationTokenSource cancelTaskTokenSource = new CancellationTokenSource();
|
|
|
|
|
private Task streamTask;
|
2020-08-13 09:24:17 +00:00
|
|
|
|
private DataMonitor dataMonitor;
|
2019-08-23 09:13:10 +00:00
|
|
|
|
|
|
|
|
|
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();
|
2019-08-25 18:02:22 +00:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
streamTask?.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException e)
|
|
|
|
|
{
|
|
|
|
|
if (e.InnerExceptions.Count == 1 && e.InnerExceptions[0] is TaskCanceledException)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
catch (TaskCanceledException) { }
|
2019-08-23 09:13:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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] : "");
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-24 15:35:41 +00:00
|
|
|
|
request.ReadWriteTimeout = 10;
|
|
|
|
|
|
2019-08-23 09:13:10 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
HttpWebResponse response;
|
|
|
|
|
|
|
|
|
|
using (cancellationToken.Register(() => request.Abort(), false))
|
|
|
|
|
{
|
|
|
|
|
response = (HttpWebResponse)await request.GetResponseAsync();
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-13 09:24:17 +00:00
|
|
|
|
|
2019-08-23 09:13:10 +00:00
|
|
|
|
if (response.StatusCode != HttpStatusCode.OK)
|
|
|
|
|
throw new WebException(response.StatusDescription);
|
|
|
|
|
|
|
|
|
|
using (var responseStream = response.GetResponseStream())
|
|
|
|
|
{
|
2020-08-13 09:24:17 +00:00
|
|
|
|
var dataMonitorCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
|
|
|
dataMonitor = new DataMonitor(dataMonitorCancellationTokenSource, TimeSpan.FromSeconds(15));
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await ReadFrames(responseStream, dataMonitorCancellationTokenSource.Token);
|
|
|
|
|
}
|
|
|
|
|
catch (TaskCanceledException)
|
|
|
|
|
{
|
|
|
|
|
if (!dataMonitorCancellationTokenSource.IsCancellationRequested)
|
|
|
|
|
throw;
|
|
|
|
|
}
|
2019-08-23 09:13:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-23 09:18:19 +00:00
|
|
|
|
catch (TaskCanceledException)
|
|
|
|
|
{
|
|
|
|
|
}
|
2019-08-23 09:13:10 +00:00
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
|
break;
|
|
|
|
|
|
2019-08-24 15:35:41 +00:00
|
|
|
|
OnStreamException(new StreamExceptionEventArgs
|
|
|
|
|
{
|
|
|
|
|
Exception = e
|
|
|
|
|
});
|
|
|
|
|
|
2019-08-23 09:13:10 +00:00
|
|
|
|
await Task.Delay(5000, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected abstract Task ReadFrames(Stream stream, CancellationToken cancellationToken);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnFrame(FrameEventArgs args)
|
|
|
|
|
{
|
2020-08-13 09:24:17 +00:00
|
|
|
|
dataMonitor.Reset();
|
2019-08-23 09:13:10 +00:00
|
|
|
|
Frame?.Invoke(this, args);
|
|
|
|
|
}
|
2019-08-24 15:35:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnStreamException(StreamExceptionEventArgs args)
|
|
|
|
|
{
|
|
|
|
|
StreamException?.Invoke(this, args);
|
|
|
|
|
}
|
2019-08-23 09:13:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2020-08-13 09:24:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2019-08-23 09:13:10 +00:00
|
|
|
|
|
2020-08-13 09:24:17 +00:00
|
|
|
|
timeoutTimer = new Timer(Tick, null, this.timeout, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void Reset()
|
|
|
|
|
{
|
|
|
|
|
timeoutTimer.Change(timeout, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void Tick(object state)
|
|
|
|
|
{
|
|
|
|
|
cancellationTokenSource.Cancel();
|
|
|
|
|
}
|
2019-08-23 09:13:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|