325 lines
12 KiB
C#
325 lines
12 KiB
C#
using IntegrationGateway.Core.Abstractions;
|
|
using IntegrationGateway.Core.Infrastructure;
|
|
using IntegrationGateway.Core.Models;
|
|
using System.Text.Json;
|
|
|
|
namespace IntegrationGateway.Adapters.Owl;
|
|
|
|
public class OwlAdapter : IHasFlatDevices, IHasStreams, IAcceptsMetadataPush
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly OwlAuthHelper _auth;
|
|
private readonly RateLimiter _limiter = new(5);
|
|
|
|
public string AdapterCode { get; }
|
|
public string DisplayName => $"Owl ({AdapterCode})";
|
|
public AdapterCapabilities Capabilities => new()
|
|
{
|
|
HasFlatDevices = true, HasStreams = true,
|
|
HasPtz = true, HasRecordings = true,
|
|
AcceptsMetadataPush = true
|
|
};
|
|
|
|
public OwlAdapter(string adapterCode, HttpClient http, string baseUrl, string username, string password)
|
|
{
|
|
AdapterCode = adapterCode;
|
|
_http = http;
|
|
_auth = new OwlAuthHelper(http, baseUrl, username, password);
|
|
}
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
await _auth.GetTokenAsync();
|
|
}
|
|
|
|
public async Task<bool> HealthCheckAsync()
|
|
{
|
|
try
|
|
{
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var resp = await client.GetAsync("/health");
|
|
return resp.IsSuccessStatusCode;
|
|
}
|
|
catch { return false; }
|
|
}
|
|
|
|
// ─── IHasFlatDevices ───
|
|
public async Task<PagedResult<StandardDevice>> GetDevicesAsync(int page, int size, string? keyword = null)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var url = $"/devices?page={page}&size={size}";
|
|
if (!string.IsNullOrEmpty(keyword)) url += $"&key={Uri.EscapeDataString(keyword)}";
|
|
var json = await client.GetStringAsync(url);
|
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlDevice>>(json)!;
|
|
return new PagedResult<StandardDevice>
|
|
{
|
|
Items = owl.Items.Select(MapDevice).ToList(),
|
|
Total = owl.Total
|
|
};
|
|
}
|
|
|
|
public async Task<StandardDevice?> GetDeviceAsync(string sourceDeviceId)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var json = await client.GetStringAsync($"/devices/{sourceDeviceId}");
|
|
var owl = JsonSerializer.Deserialize<OwlDevice>(json);
|
|
return owl is null ? null : MapDevice(owl);
|
|
}
|
|
|
|
public async Task<List<StandardDevice>> GetAllDevicesAsync()
|
|
{
|
|
var result = new List<StandardDevice>();
|
|
int page = 1;
|
|
while (true)
|
|
{
|
|
var paged = await GetDevicesAsync(page, 50);
|
|
result.AddRange(paged.Items);
|
|
if (result.Count >= paged.Total) break;
|
|
page++;
|
|
}
|
|
// Also get channels for each NVR
|
|
var channels = await GetAllChannelsAsync();
|
|
result.AddRange(channels);
|
|
return result;
|
|
}
|
|
|
|
public async Task<PagedResult<StandardDevice>> GetChannelsAsync(int page, int size, string? parentDeviceId = null)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var url = $"/channels?page={page}&size={size}";
|
|
if (!string.IsNullOrEmpty(parentDeviceId))
|
|
{
|
|
var resp = await client.GetStringAsync($"/devices/{parentDeviceId}/channels");
|
|
var channels = JsonSerializer.Deserialize<OwlPagedResult<OwlChannel>>(resp)!;
|
|
return new PagedResult<StandardDevice>
|
|
{
|
|
Items = channels.Items.Select(c => MapChannel(c)).ToList(),
|
|
Total = channels.Total
|
|
};
|
|
}
|
|
var json = await client.GetStringAsync(url);
|
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlChannel>>(json)!;
|
|
return new PagedResult<StandardDevice>
|
|
{
|
|
Items = owl.Items.Select(c => MapChannel(c)).ToList(),
|
|
Total = owl.Total
|
|
};
|
|
}
|
|
|
|
public async Task<List<StandardDevice>> GetAllChannelsAsync()
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var json = await client.GetStringAsync("/channels?size=1000");
|
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlChannel>>(json)!;
|
|
return owl.Items.Select(c => MapChannel(c)).ToList();
|
|
}
|
|
|
|
// ─── IHasStreams ───
|
|
public async Task<StreamUrls> GetLiveUrlAsync(string channelId)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var resp = await client.PostAsync($"/channels/{channelId}/play", null);
|
|
resp.EnsureSuccessStatusCode();
|
|
var json = await resp.Content.GetStringAsync();
|
|
var play = JsonSerializer.Deserialize<OwlPlayResponse>(json)!;
|
|
return MapStreamUrls(play);
|
|
}
|
|
|
|
public async Task<StreamUrls> GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var startMs = new DateTimeOffset(start).ToUnixTimeMilliseconds();
|
|
var endMs = new DateTimeOffset(end).ToUnixTimeMilliseconds();
|
|
var token = await _auth.GetTokenAsync();
|
|
return new StreamUrls
|
|
{
|
|
Hls = $"{client.BaseAddress}recordings/channels/{channelId}/index.m3u8?start_ms={startMs}&end_ms={endMs}&token={token}"
|
|
};
|
|
}
|
|
|
|
public async Task StopPlayAsync(string channelId)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
await client.PostAsync($"/channels/{channelId}/stop", null);
|
|
}
|
|
|
|
public async Task<StreamUrls> GetSnapshotAsync(string channelId)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var resp = await client.PostAsync($"/channels/{channelId}/snapshot",
|
|
new StringContent("{}", System.Text.Encoding.UTF8, "application/json"));
|
|
var json = await resp.Content.ReadAsStringAsync();
|
|
var snap = JsonSerializer.Deserialize<OwlSnapshotResponse>(json)!;
|
|
return new StreamUrls { Hls = snap.Link };
|
|
}
|
|
|
|
public async Task PtzControlAsync(string channelId, string direction, float speed)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control", new
|
|
{
|
|
action = "continuous",
|
|
direction,
|
|
speed
|
|
});
|
|
}
|
|
|
|
public async Task PtzStopAsync(string channelId)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control", new
|
|
{
|
|
action = "stop"
|
|
});
|
|
}
|
|
|
|
public async Task<PagedResult<StandardRecording>> GetRecordingsAsync(string channelId, DateTime start, DateTime end, int page, int size)
|
|
{
|
|
await _limiter.WaitAsync();
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var startMs = new DateTimeOffset(start).ToUnixTimeMilliseconds();
|
|
var endMs = new DateTimeOffset(end).ToUnixTimeMilliseconds();
|
|
var json = await client.GetStringAsync(
|
|
$"/recordings?cid={channelId}&start_ms={startMs}&end_ms={endMs}&page={page}&size={size}");
|
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlRecording>>(json)!;
|
|
return new PagedResult<StandardRecording>
|
|
{
|
|
Items = owl.Items.Select(r => new StandardRecording
|
|
{
|
|
Id = r.Id.ToString(),
|
|
ChannelId = r.Cid ?? "",
|
|
StartedAt = r.StartedAt,
|
|
EndedAt = r.EndedAt,
|
|
Duration = r.Duration,
|
|
FilePath = r.Path,
|
|
Size = r.Size
|
|
}).ToList(),
|
|
Total = owl.Total
|
|
};
|
|
}
|
|
|
|
// ─── IAcceptsMetadataPush ───
|
|
public async Task<MetadataPushResult> PushMetadataAsync(string sourceDeviceId, MetadataChangeSet changes)
|
|
{
|
|
var client = await _auth.GetAuthenticatedClientAsync();
|
|
var body = new Dictionary<string, object>();
|
|
if (changes.Name != null) body["name"] = changes.Name;
|
|
var resp = await client.PutAsJsonAsync($"/devices/{sourceDeviceId}", body);
|
|
return new MetadataPushResult { Success = resp.IsSuccessStatusCode };
|
|
}
|
|
|
|
// ─── Mapping ───
|
|
private static StandardDevice MapDevice(OwlDevice d) => new()
|
|
{
|
|
SourceId = d.Id ?? "",
|
|
Name = d.Name ?? d.Id ?? "",
|
|
Category = "硬盘录像机",
|
|
Group = "视频设备",
|
|
IsOnline = d.IsOnline == "1",
|
|
IsParent = true,
|
|
IpAddress = d.Address,
|
|
Port = int.TryParse(d.Port, out var port) ? port : null,
|
|
Extra = new Dictionary<string, object?>
|
|
{
|
|
["owlDeviceId"] = d.Id,
|
|
["protocol"] = d.Protocol ?? "GB28181",
|
|
["transport"] = d.Transport
|
|
}
|
|
};
|
|
|
|
private static StandardDevice MapChannel(OwlChannel c) => new()
|
|
{
|
|
SourceId = c.Id ?? "",
|
|
Name = c.Name ?? c.Id ?? "",
|
|
Category = "摄像机",
|
|
Group = "视频设备",
|
|
IsOnline = c.IsOnline == "1",
|
|
IsParent = false,
|
|
ParentSourceId = c.DeviceId,
|
|
Extra = new Dictionary<string, object?>
|
|
{
|
|
["owlChannelId"] = c.Id,
|
|
["streamApp"] = c.App,
|
|
["streamName"] = c.Stream,
|
|
["hasPtz"] = c.HasPtz == "1"
|
|
}
|
|
};
|
|
|
|
private static StreamUrls MapStreamUrls(OwlPlayResponse play)
|
|
{
|
|
var item = play.Items?.FirstOrDefault();
|
|
return new StreamUrls
|
|
{
|
|
WsFlv = item?.WsFlv,
|
|
HttpFlv = item?.HttpFlv,
|
|
Hls = item?.Hls,
|
|
WebRtc = item?.WebRtc,
|
|
Rtmp = item?.Rtmp,
|
|
Rtsp = item?.Rtsp
|
|
};
|
|
}
|
|
}
|
|
|
|
// ─── Owl JSON Models ───
|
|
public class OwlPagedResult<T> { public List<T> Items { get; set; } = new(); public int Total { get; set; } }
|
|
public class OwlDevice
|
|
{
|
|
public string? Id { get; set; }
|
|
public string? Name { get; set; }
|
|
public string? IsOnline { get; set; }
|
|
public string? Protocol { get; set; }
|
|
public string? Address { get; set; }
|
|
public string? Port { get; set; }
|
|
public string? Transport { get; set; }
|
|
public int Channels { get; set; }
|
|
}
|
|
public class OwlChannel
|
|
{
|
|
public string? Id { get; set; }
|
|
public string? DeviceId { get; set; }
|
|
public string? Name { get; set; }
|
|
public string? IsOnline { get; set; }
|
|
public string? App { get; set; }
|
|
public string? Stream { get; set; }
|
|
public string? HasPtz { get; set; }
|
|
public string? HasRecording { get; set; }
|
|
public int RecordMode { get; set; }
|
|
}
|
|
public class OwlPlayResponse
|
|
{
|
|
public string? App { get; set; }
|
|
public string? Stream { get; set; }
|
|
public List<OwlPlayItem>? Items { get; set; }
|
|
}
|
|
public class OwlPlayItem
|
|
{
|
|
public string? Label { get; set; }
|
|
public string? WsFlv { get; set; }
|
|
public string? HttpFlv { get; set; }
|
|
public string? Hls { get; set; }
|
|
public string? WebRtc { get; set; }
|
|
public string? Rtmp { get; set; }
|
|
public string? Rtsp { get; set; }
|
|
}
|
|
public class OwlSnapshotResponse { public string? Link { get; set; } }
|
|
public class OwlRecording
|
|
{
|
|
public int Id { get; set; }
|
|
public string? Cid { get; set; }
|
|
public DateTime StartedAt { get; set; }
|
|
public DateTime EndedAt { get; set; }
|
|
public double Duration { get; set; }
|
|
public string? Path { get; set; }
|
|
public long Size { get; set; }
|
|
}
|