diff --git a/Cis.Application/Cis.Application.xml b/Cis.Application/Cis.Application.xml index 51a88ed..062d2e3 100644 --- a/Cis.Application/Cis.Application.xml +++ b/Cis.Application/Cis.Application.xml @@ -530,6 +530,11 @@ Onvif Api 分组排序 + + + ZLMediaKit Api 分组排序 + + 循环间隔,单位毫秒 @@ -1051,6 +1056,21 @@ {cameraId, OnvifClient} + + + 虚拟主机 + + + + + 应用名 + + + + + 流id + + 标签追踪服务 @@ -1222,6 +1242,41 @@ cbCameraId + + + zlmediakit 服务 + + + + + 添加码流拉流代理(只支持H264/H265/aac/G711负载) + + cbCameraId + 码流级别[0,2], default:0 + + + + + 关闭拉流代理 + + cbCameraId + 码流级别[0,2], default:0 + + + + + 获取流列表 + + cbCameraId + 码流级别[0,2], default:0 + + + + 判断直播流是否在线 + + cbCameraId + 码流级别[0,2], default:0 + 配置应用所需服务,在该方法中可以添加应用所需要的功能或服务 diff --git a/Cis.Application/Core/Common/CoreInfo.cs b/Cis.Application/Core/Common/CoreInfo.cs index d39092b..22a0271 100644 --- a/Cis.Application/Core/Common/CoreInfo.cs +++ b/Cis.Application/Core/Common/CoreInfo.cs @@ -19,5 +19,10 @@ public class CoreInfo /// public const int OnvifGroupOrder = 100; + /// + /// ZLMediaKit Api 分组排序 + /// + public const int ZlmGroupOrder = 100; + #endregion Api Info } \ No newline at end of file diff --git a/Cis.Application/Core/Component/ZLMediaKit/Entity/StreamConnInfo.cs b/Cis.Application/Core/Component/ZLMediaKit/Entity/StreamConnInfo.cs new file mode 100644 index 0000000..cedcc23 --- /dev/null +++ b/Cis.Application/Core/Component/ZLMediaKit/Entity/StreamConnInfo.cs @@ -0,0 +1,24 @@ +namespace Cis.Application.Core.Component.ZLMediaKit; + +public class StreamConnInfo +{ + /// + /// 虚拟主机 + /// + public string Vhost { get; set; } + + /// + /// 应用名 + /// + public string App { get; set; } + + /// + /// 流id + /// + public string Stream { get; set; } + + public static StreamConnInfo New(string vhost, string app, string stream) + { + return new() { Vhost = vhost, App = app, Stream = stream }; + } +} \ No newline at end of file diff --git a/Cis.Application/Core/Component/ZLMediaKit/Entity/ZlmException.cs b/Cis.Application/Core/Component/ZLMediaKit/Entity/ZlmException.cs new file mode 100644 index 0000000..f23d1a0 --- /dev/null +++ b/Cis.Application/Core/Component/ZLMediaKit/Entity/ZlmException.cs @@ -0,0 +1,66 @@ +using System.Text; + +namespace Cis.Application.Core.Component.ZLMediaKit; + +public class ZlmException : Exception +{ + public ZlmException() : base() + { + } + + public ZlmException(string message) : base(message) + { + } + + public ZlmException(string message, Exception innerException) : base(message, innerException) + { + } + + protected class ZlmExceptionObj + { + public ZlmCode Code { get; set; } + + public string Msg { get; set; } + + public int Result { get; set; } + + public override string ToString() + { + StringBuilder builder = new(); + builder.Append($"Code:{Code.ToInt()}({Code}), Msg:{Msg}"); + if (Result < 0) builder.Append($", Result:{Result}"); + return builder.ToString(); + } + } + + public static ZlmException New(ZlmCode code, string msg) + { + ZlmExceptionObj obj = new() + { + Code = code, + Msg = msg + }; + return new ZlmException(obj.ToString()); + } + + public static ZlmException New(ZlmCode code, string msg, int result) + { + ZlmExceptionObj obj = new() + { + Code = code, + Msg = msg, + Result = result + }; + return new ZlmException(obj.ToString()); + } +} + +public enum ZlmCode +{ + Success = 0, //执行成功 + OtherFailed = -1, //业务代码执行失败 + AuthFailed = -100, //鉴权失败 + SqlFailed = -200, //sql执行失败 + InvalidArgs = -300, //参数不合法 + Exception = -400, //代码抛异常 +} \ No newline at end of file diff --git a/Cis.Application/Core/Component/ZLMediaKit/IZlmServer.cs b/Cis.Application/Core/Component/ZLMediaKit/IZlmServer.cs new file mode 100644 index 0000000..dc5dd69 --- /dev/null +++ b/Cis.Application/Core/Component/ZLMediaKit/IZlmServer.cs @@ -0,0 +1,18 @@ +namespace Cis.Application.Core.Component.ZLMediaKit; + +public interface IZlmServer +{ + #region Base Method + + public Task AddStreamProxy(string stream, string rtspUrl); + + public Task DelStreamProxy(string stream); + + public Task GetMediaList(string stream); + + public Task IsMediaOnline(string stream); + + public StreamConnInfo GetStreamConnInfo(string stream); + + #endregion Base Method +} \ No newline at end of file diff --git a/Cis.Application/Core/Component/ZLMediaKit/ZlmServer.cs b/Cis.Application/Core/Component/ZLMediaKit/ZlmServer.cs new file mode 100644 index 0000000..06472c4 --- /dev/null +++ b/Cis.Application/Core/Component/ZLMediaKit/ZlmServer.cs @@ -0,0 +1,127 @@ +using Furion.RemoteRequest.Extensions; +using Newtonsoft.Json.Linq; + +namespace Cis.Application.Core.Component.ZLMediaKit; + +public class ZlmServer : IZlmServer, ISingleton +{ + #region Attr + + private readonly ZLMediaKitOptions _options; + + #region Url + + private string AddStreamProxyUrl { get; set; } + + private string DelStreamProxyUrl { get; set; } + + private string GetMediaListUrl { get; set; } + + private string IsMediaOnlineUrl { get; set; } + + #endregion Url + + #endregion Attr + + public ZlmServer() + { + _options = App.GetOptions(); + InitUrl(); + } + + #region Util Method + + private void InitUrl() + { + string baseUrl = $"http://{_options.Ip}:{_options.Port}"; + AddStreamProxyUrl = $"{baseUrl}/index/api/addStreamProxy"; + DelStreamProxyUrl = $"{baseUrl}/index/api/delStreamProxy"; + GetMediaListUrl = $"{baseUrl}/index/api/getMediaList"; + IsMediaOnlineUrl = $"{baseUrl}/index/api/isMediaOnline"; + } + + private bool JudgeResult(JObject data) + { + ZlmCode code = (ZlmCode)data["code"].ToInt(); + if (code == ZlmCode.Success) return false; + string msg = data["msg"].ToString(); + int result = data["result"].ToInt(); + throw ZlmException.New(code, msg, result); + } + + #endregion Util Method + + #region Base Method + + public async Task AddStreamProxy(string stream, string rtspUrl) + { + string resp = await AddStreamProxyUrl.SetBody(new + { + secret = _options.Secret, + vhost = _options.DefaultVhost, + app = _options.DefaultApp, + stream = stream, + url = rtspUrl, + rtp_type = _options.RtpType, + timeout_sec = _options.TimeoutSec, + retry_count = _options.RetryCount, + enable_rtmp = 1, + enable_hls = 0, + enable_mp4 = 0, + enable_ts = 0, + enable_fmp4 = 0, + }).PostAsStringAsync(); + JObject respJObj = resp.ToJObject(); + JudgeResult(respJObj); + return StreamConnInfo.New(_options.DefaultVhost, _options.DefaultApp, stream); + } + + public async Task DelStreamProxy(string stream) + { + string resp = await DelStreamProxyUrl.SetBody(new + { + secret = _options.Secret, + key = $"{_options.DefaultVhost}/{_options.DefaultApp}/{stream}", + }).PostAsStringAsync(); + JObject respJObj = resp.ToJObject(); + JudgeResult(respJObj); + return respJObj["data"]["flag"].ToBoolean(); + } + + public async Task GetMediaList(string stream) + { + string resp = await GetMediaListUrl.SetBody(new + { + secret = _options.Secret, + vhost = _options.DefaultVhost, + app = _options.DefaultApp, + stream = stream, + schema = "rtsp", + }).PostAsStringAsync(); + JObject respJObj = resp.ToJObject(); + JudgeResult(respJObj); + return respJObj["data"]?.ToObject(); + } + + public async Task IsMediaOnline(string stream) + { + string resp = await IsMediaOnlineUrl.SetBody(new + { + secret = _options.Secret, + vhost = _options.DefaultVhost, + app = _options.DefaultApp, + stream = stream, + schema = "rtsp", + }).PostAsStringAsync(); + JObject respJObj = resp.ToJObject(); + JudgeResult(respJObj); + return respJObj["online"].ToBoolean(); + } + + public StreamConnInfo GetStreamConnInfo(string stream) + { + return StreamConnInfo.New(_options.DefaultVhost, _options.DefaultApp, stream); + } + + #endregion Base Method +} \ No newline at end of file diff --git a/Cis.Application/Core/Service/OnvifService.cs b/Cis.Application/Core/Service/OnvifService.cs index b4687b1..d429a3e 100644 --- a/Cis.Application/Core/Service/OnvifService.cs +++ b/Cis.Application/Core/Service/OnvifService.cs @@ -14,11 +14,11 @@ public class OnvifService : IDynamicApiController, ITransient private readonly SqlSugarRepository _cbCameraRep; - private readonly OnvifServer _onvifServer; + private readonly IOnvifServer _onvifServer; #endregion - public OnvifService(SqlSugarRepository cbCameraRep, OnvifServer onvifServer) + public OnvifService(SqlSugarRepository cbCameraRep, IOnvifServer onvifServer) { _cbCameraRep = cbCameraRep; _onvifServer = onvifServer; @@ -75,6 +75,7 @@ public class OnvifService : IDynamicApiController, ITransient /// 变焦移动绝对点:[-1,1] /// [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
position:变焦移动绝对点:[-1,1]")] public async Task FocusAbsoluteMove([Required][FromForm] long cameraId, [Required][FromForm] float position) { bool ret = _onvifServer.TryGet(cameraId, out OnvifClient client); @@ -90,6 +91,7 @@ public class OnvifService : IDynamicApiController, ITransient /// 变焦移动相对点:[-1,1] /// [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
distance:变焦移动相对点:[-1,1]")] public async Task FocusRelativeMove([Required][FromForm] long cameraId, [Required][FromForm] float distance) { bool ret = _onvifServer.TryGet(cameraId, out OnvifClient client); @@ -105,6 +107,7 @@ public class OnvifService : IDynamicApiController, ITransient /// 持续移动方向:[-1,1] /// [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
speed:持续移动方向:[-1,1]")] public async Task FocusContinuousMove([Required][FromForm] long cameraId, [Required][FromForm] float speed) { bool ret = _onvifServer.TryGet(cameraId, out OnvifClient client); @@ -118,6 +121,7 @@ public class OnvifService : IDynamicApiController, ITransient /// /// cbCameraId [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId")] public async Task FocusStopMove([Required][FromForm] long cameraId) { bool ret = _onvifServer.TryGet(cameraId, out OnvifClient client); @@ -186,6 +190,8 @@ public class OnvifService : IDynamicApiController, ITransient /// 可以理解为移动速度:[0,1],默认 0.1 /// [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
pan:水平方向移动绝对点:[-1,1]
tilt:垂直方向移动绝对点:[-1,1]
" + + "zoom:变倍绝对点:[-1,1]
atomDist:可以理解为移动速度:[0,1],默认 0.1")] public async Task AbsoluteMove([Required][FromForm] long cameraId, [Required][FromForm] float pan, [Required][FromForm] float tilt, [Required][FromForm] float zoom, [FromForm] float atomDist = 0.1f) { @@ -204,6 +210,8 @@ public class OnvifService : IDynamicApiController, ITransient /// 变倍相对点:[-1,1] /// 移动速度:[0,1],默认 0.1 [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
pan:水平方向移动相对点:[-1,1]
tilt:垂直方向移动相对点:[-1,1]
" + + "zoom:变倍相对点:[-1,1]
atomSpeed:移动速度:[0,1],默认 0.1")] public async Task RelativeMove([Required][FromForm] long cameraId, [Required][FromForm] float pan, [Required][FromForm] float tilt, [Required][FromForm] float zoom, [FromForm] float atomSpeed = 0.1f) { @@ -222,6 +230,8 @@ public class OnvifService : IDynamicApiController, ITransient /// 变倍移动方向:[-1,1] /// 超时时间,ms [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
pan:水平方向移动方向:[-1,1]
tilt:垂直方向移动方向:[-1,1]
" + + "zoom:变倍移动方向:[-1,1]
timeout:超时时间,ms")] public async Task ContinuousMove([Required][FromForm] long cameraId, [Required][FromForm] float pan, [Required][FromForm] float tilt, [Required][FromForm] float zoom, [FromForm] string timeout = "") { @@ -236,6 +246,7 @@ public class OnvifService : IDynamicApiController, ITransient /// /// cbCameraId [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId")] public async Task StopMove([Required][FromForm] long cameraId) { bool ret = _onvifServer.TryGet(cameraId, out OnvifClient client); diff --git a/Cis.Application/Core/Service/ZlmService.cs b/Cis.Application/Core/Service/ZlmService.cs new file mode 100644 index 0000000..c434ccf --- /dev/null +++ b/Cis.Application/Core/Service/ZlmService.cs @@ -0,0 +1,111 @@ +using Cis.Application.Cb; +using Cis.Application.Core.Component.Onvif; +using Cis.Application.Core.Component.ZLMediaKit; +using EC.Helper.Onvif; +using Furion.DataEncryption; + +namespace Cis.Application.Core.Service; + +/// +/// zlmediakit 服务 +/// +[ApiDescriptionSettings(CoreInfo.GroupName, Order = CoreInfo.ZlmGroupOrder)] +public class ZlmService : IDynamicApiController, ITransient +{ + #region Attr + + private readonly SqlSugarRepository _cbCameraRep; + + private readonly IZlmServer _zlmServer; + + private readonly IOnvifServer _onvifServer; + + #endregion Attr + + public ZlmService(SqlSugarRepository cbCameraRep, IZlmServer zlmServer, IOnvifServer onvifServer) + { + _cbCameraRep = cbCameraRep; + _zlmServer = zlmServer; + _onvifServer = onvifServer; + } + + #region Base Method + + /// + /// 添加码流拉流代理(只支持H264/H265/aac/G711负载) + /// + /// cbCameraId + /// 码流级别[0,2], default:0 + /// + [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
streamLevel:码流级别[0,2],default:0")] + public async Task AddStreamProxy([Required][FromForm] long cameraId, [FromForm][Range(0, 2)] int streamLevel = 0) + { + CbCamera camera = await _cbCameraRep.GetByIdAsync(cameraId); + if (camera == null) return default; + string stream = MD5Encryption.Encrypt($"{camera.Ip}:{streamLevel}"); + bool isOnline = await _zlmServer.IsMediaOnline(stream); + if (isOnline) return _zlmServer.GetStreamConnInfo(stream); + bool ret = _onvifServer.IsExists(camera.Id); + if (!ret) ret = await _onvifServer.RegisterAsync(camera); + if (!ret) return default; + ret = _onvifServer.TryGet(cameraId, out OnvifClient client); + if (!ret) return default; + string rtspUrl = streamLevel switch + { + 0 => await client.GetStreamUrl(), + 1 => await client.GetSubStreamUrl(), + 2 => await client.GetThirdStreamUrl(), + _ => await client.GetStreamUrl() + }; + return await _zlmServer.AddStreamProxy(stream, rtspUrl); + } + + /// + /// 关闭拉流代理 + /// + /// cbCameraId + /// 码流级别[0,2], default:0 + /// + [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
streamLevel:码流级别[0,2],default:0")] + public async Task DelStreamProxy([Required][FromForm] long cameraId, [FromForm][Range(0, 2)] int streamLevel = 0) + { + CbCamera camera = await _cbCameraRep.GetByIdAsync(cameraId); + if (camera == null) return false; + string stream = MD5Encryption.Encrypt($"{camera.Ip}:{streamLevel}"); + return await _zlmServer.DelStreamProxy(stream); + } + + /// + /// 获取流列表 + /// + /// cbCameraId + /// 码流级别[0,2], default:0 + [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
streamLevel:码流级别[0,2],default:0")] + public async Task GetMediaList([Required][FromForm] long cameraId, [FromForm][Range(0, 2)] int streamLevel = 0) + { + CbCamera camera = await _cbCameraRep.GetByIdAsync(cameraId); + if (camera == null) return default; + string stream = MD5Encryption.Encrypt($"{camera.Ip}:{streamLevel}"); + return await _zlmServer.GetMediaList(stream); + } + + /// + /// 判断直播流是否在线 + /// + /// cbCameraId + /// 码流级别[0,2], default:0 + [HttpPost] + [ApiDescriptionSettings(Description = "cameraId:cbCameraId
streamLevel:码流级别[0,2],default:0")] + public async Task IsMediaOnline([Required][FromForm] long cameraId, [FromForm][Range(0, 2)] int streamLevel = 0) + { + CbCamera camera = await _cbCameraRep.GetByIdAsync(cameraId); + if (camera == null) return false; + string stream = MD5Encryption.Encrypt($"{camera.Ip}:{streamLevel}"); + return await _zlmServer.IsMediaOnline(stream); + } + + #endregion Base Method +} \ No newline at end of file diff --git a/Cis.Application/Startup.cs b/Cis.Application/Startup.cs index c68d577..413797c 100644 --- a/Cis.Application/Startup.cs +++ b/Cis.Application/Startup.cs @@ -3,6 +3,7 @@ using Cis.Application.Core.Component.CameraSDK; using Cis.Application.Core.Component.MarkSeacher; using Cis.Application.Core.Component.Onvif; using Cis.Application.Core.Component.PtzServer; +using Cis.Application.Core.Component.ZLMediaKit; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +23,7 @@ public class Startup : AppStartup services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(typeof(CameraDataCenter)); } diff --git a/Cis.Core/Cis.Core.csproj b/Cis.Core/Cis.Core.csproj index 27d689b..fa65bc1 100644 --- a/Cis.Core/Cis.Core.csproj +++ b/Cis.Core/Cis.Core.csproj @@ -34,6 +34,6 @@ - + diff --git a/Cis.Core/Cis.Core.xml b/Cis.Core/Cis.Core.xml index dd0ecea..08cec37 100644 --- a/Cis.Core/Cis.Core.xml +++ b/Cis.Core/Cis.Core.xml @@ -403,6 +403,46 @@ 机器码 + + + 服务IP + + + + + 服务端口 + + + + + 服务鉴权秘钥 + + + + + 默认虚拟主机 + + + + + 默认应用名 + + + + + rtsp拉流时,拉流方式 + + + + + 拉流超时时间,单位秒,float类型 + + + + + 拉流重试次数 + + 自定义实体过滤器接口 diff --git a/Cis.Core/Common/Option/ZLMediaKitOptions.cs b/Cis.Core/Common/Option/ZLMediaKitOptions.cs new file mode 100644 index 0000000..88176b1 --- /dev/null +++ b/Cis.Core/Common/Option/ZLMediaKitOptions.cs @@ -0,0 +1,44 @@ +namespace Cis.Core; + +public class ZLMediaKitOptions : IConfigurableOptions +{ + /// + /// 服务IP + /// + public string Ip { get; set; } + + /// + /// 服务端口 + /// + public string Port { get; set; } + + /// + /// 服务鉴权秘钥 + /// + public string Secret { get; set; } + + /// + /// 默认虚拟主机 + /// + public string DefaultVhost { get; set; } + + /// + /// 默认应用名 + /// + public string DefaultApp { get; set; } + + /// + /// rtsp拉流时,拉流方式 + /// + public string RtpType { get; set; } + + /// + /// 拉流超时时间,单位秒,float类型 + /// + public float TimeoutSec { get; set; } + + /// + /// 拉流重试次数 + /// + public int RetryCount { get; set; } +} \ No newline at end of file diff --git a/Cis.Core/CoreConfig.json b/Cis.Core/CoreConfig.json index 485f006..afe0617 100644 --- a/Cis.Core/CoreConfig.json +++ b/Cis.Core/CoreConfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json", "DbConnection": { "ConnectionConfigs": [ { @@ -16,6 +17,16 @@ "Redis": { "ConnectionString": "192.168.1.119:6379,password=123456,defaultDatabase=2" }, + "ZLMediaKit": { + "Ip": "192.168.1.119", //ip + "Port": "8080", //端口 + "Secret": "035c73f7-bb6b-4889-a715-d9eb2d1925cc", //鉴权秘钥 + "DefaultVhost": "__defaultVhost__", //默认虚拟主机 + "DefaultApp": "live", //默认应用名 + "RtpType": "0", //rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播 + "TimeoutSec": 5, //拉流超时时间,单位秒,float类型 + "RetryCount": 3 //拉流重试次数 + }, "AppSettings": { "InjectSpecificationDocument": true // 生产环境是否开启Swagger }, diff --git a/Cis.Web.Core/ProjectOptions.cs b/Cis.Web.Core/ProjectOptions.cs index 6fdaaee..13cf371 100644 --- a/Cis.Web.Core/ProjectOptions.cs +++ b/Cis.Web.Core/ProjectOptions.cs @@ -15,6 +15,7 @@ public static class ProjectOptions services.AddConfigurableOptions(); services.AddConfigurableOptions(); services.AddConfigurableOptions(); + services.AddConfigurableOptions(); return services; } diff --git a/EC.Helper/CameraSDK/Common/CameraException.cs b/EC.Helper/CameraSDK/Common/CameraException.cs index e2a392e..2e7c9e6 100644 --- a/EC.Helper/CameraSDK/Common/CameraException.cs +++ b/EC.Helper/CameraSDK/Common/CameraException.cs @@ -1,4 +1,6 @@ -namespace EC.Helper.CameraSDK; +using System.Text; + +namespace EC.Helper.CameraSDK; /// /// 相机异常 @@ -21,33 +23,36 @@ public class CameraException : Exception { public CameraManufactor Manufactor { get; set; } - public int ErrCode { get; set; } + public int Code { get; set; } - public string? ErrMsg { get; set; } + public string? Msg { get; set; } public override string? ToString() { - return $"Manufactor:{Manufactor}, ErrCode:{ErrCode}, ErrMsg:{ErrMsg}"; + StringBuilder builder = new(); + builder.Append($"Manufactor:{Manufactor}, Code:{Code}"); + if (!string.IsNullOrEmpty(Msg)) builder.Append($", Msg:{Msg}"); + return builder.ToString(); } } - public static CameraException New(CameraManufactor manufactor, int errCode) + public static CameraException New(CameraManufactor manufactor, int code) { CameraExceptionObj obj = new() { Manufactor = manufactor, - ErrCode = errCode + Code = code }; return new CameraException(obj.ToString()); } - public static CameraException New(CameraManufactor manufactor, int errCode, string errMsg) + public static CameraException New(CameraManufactor manufactor, int code, string msg) { CameraExceptionObj obj = new() { Manufactor = manufactor, - ErrCode = errCode, - ErrMsg = errMsg + Code = code, + Msg = msg }; return new CameraException(obj.ToString()); } diff --git a/EC.Helper/Onvif/OnvifClient.cs b/EC.Helper/Onvif/OnvifClient.cs index 9c5b50a..9d129ed 100644 --- a/EC.Helper/Onvif/OnvifClient.cs +++ b/EC.Helper/Onvif/OnvifClient.cs @@ -38,10 +38,18 @@ public class OnvifClient protected string ProfileToken { get; set; } + protected string SubProfileToken { get; set; } + + protected string ThirdProfileToken { get; set; } + protected string VideoSourceToken { get; set; } protected string SteamUrl { get; set; } + protected string SubSteamUrl { get; set; } + + protected string ThirdSteamUrl { get; set; } + protected string SnapshotUrl { get; set; } #endregion Cache Attr @@ -69,9 +77,19 @@ public class OnvifClient if (string.IsNullOrEmpty(ProfileToken)) { ProfileToken = profile.token; + } + else if (string.IsNullOrEmpty(SubProfileToken)) + { + SubProfileToken = profile.token; + } + else if (string.IsNullOrEmpty(ThirdProfileToken)) + { + ThirdProfileToken = profile.token; break; } } + if (string.IsNullOrEmpty(SubProfileToken)) SubProfileToken = ProfileToken; + if (string.IsNullOrEmpty(ThirdProfileToken)) ThirdProfileToken = SubProfileToken; foreach (var source in videoSources.VideoSources) { @@ -125,6 +143,26 @@ public class OnvifClient return SteamUrl; } + public async Task GetSubStreamUrl() + { + if (string.IsNullOrEmpty(SubSteamUrl)) + { + MediaUri mediaUri = await Media.GetStreamUriAsync(RtspStreamSetup, SubProfileToken); + SubSteamUrl = mediaUri.Uri.Replace("://", $"://{Username}:{Password}@"); + } + return SubSteamUrl; + } + + public async Task GetThirdStreamUrl() + { + if (string.IsNullOrEmpty(ThirdSteamUrl)) + { + MediaUri mediaUri = await Media.GetStreamUriAsync(RtspStreamSetup, SubProfileToken); + ThirdSteamUrl = mediaUri.Uri.Replace("://", $"://{Username}:{Password}@"); + } + return ThirdSteamUrl; + } + public async Task GetSnapshotUrl() { if (string.IsNullOrEmpty(SnapshotUrl))