Files
SecMPS/gateway/src/IntegrationGateway.Adapters.Owl/OwlAdapter.cs
2026-05-16 23:32:50 +08:00

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