fajiao
2 years ago
16 changed files with 570 additions and 12 deletions
@ -0,0 +1,24 @@ |
|||||
|
namespace Cis.Application.Core.Component.ZLMediaKit; |
||||
|
|
||||
|
public class StreamConnInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 虚拟主机
|
||||
|
/// </summary>
|
||||
|
public string Vhost { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 应用名
|
||||
|
/// </summary>
|
||||
|
public string App { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 流id
|
||||
|
/// </summary>
|
||||
|
public string Stream { get; set; } |
||||
|
|
||||
|
public static StreamConnInfo New(string vhost, string app, string stream) |
||||
|
{ |
||||
|
return new() { Vhost = vhost, App = app, Stream = stream }; |
||||
|
} |
||||
|
} |
@ -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, //代码抛异常
|
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
namespace Cis.Application.Core.Component.ZLMediaKit; |
||||
|
|
||||
|
public interface IZlmServer |
||||
|
{ |
||||
|
#region Base Method
|
||||
|
|
||||
|
public Task<StreamConnInfo> AddStreamProxy(string stream, string rtspUrl); |
||||
|
|
||||
|
public Task<bool> DelStreamProxy(string stream); |
||||
|
|
||||
|
public Task<object> GetMediaList(string stream); |
||||
|
|
||||
|
public Task<bool> IsMediaOnline(string stream); |
||||
|
|
||||
|
public StreamConnInfo GetStreamConnInfo(string stream); |
||||
|
|
||||
|
#endregion Base Method
|
||||
|
} |
@ -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<ZLMediaKitOptions>(); |
||||
|
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<StreamConnInfo> 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<bool> 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<object> 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<object>(); |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> 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
|
||||
|
} |
@ -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; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// zlmediakit 服务
|
||||
|
/// </summary>
|
||||
|
[ApiDescriptionSettings(CoreInfo.GroupName, Order = CoreInfo.ZlmGroupOrder)] |
||||
|
public class ZlmService : IDynamicApiController, ITransient |
||||
|
{ |
||||
|
#region Attr
|
||||
|
|
||||
|
private readonly SqlSugarRepository<CbCamera> _cbCameraRep; |
||||
|
|
||||
|
private readonly IZlmServer _zlmServer; |
||||
|
|
||||
|
private readonly IOnvifServer _onvifServer; |
||||
|
|
||||
|
#endregion Attr
|
||||
|
|
||||
|
public ZlmService(SqlSugarRepository<CbCamera> cbCameraRep, IZlmServer zlmServer, IOnvifServer onvifServer) |
||||
|
{ |
||||
|
_cbCameraRep = cbCameraRep; |
||||
|
_zlmServer = zlmServer; |
||||
|
_onvifServer = onvifServer; |
||||
|
} |
||||
|
|
||||
|
#region Base Method
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 添加码流拉流代理(只支持H264/H265/aac/G711负载)
|
||||
|
/// </summary>
|
||||
|
/// <param name="cameraId">cbCameraId</param>
|
||||
|
/// <param name="streamLevel">码流级别[0,2], default:0</param>
|
||||
|
/// <returns></returns>
|
||||
|
[HttpPost] |
||||
|
[ApiDescriptionSettings(Description = "cameraId:cbCameraId<br/>streamLevel:码流级别[0,2],default:0")]
|
||||
|
public async Task<StreamConnInfo> 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); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 关闭拉流代理
|
||||
|
/// </summary>
|
||||
|
/// <param name="cameraId">cbCameraId</param>
|
||||
|
/// <param name="streamLevel">码流级别[0,2], default:0</param>
|
||||
|
/// <returns></returns>
|
||||
|
[HttpPost] |
||||
|
[ApiDescriptionSettings(Description = "cameraId:cbCameraId<br/>streamLevel:码流级别[0,2],default:0")]
|
||||
|
public async Task<bool> 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); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 获取流列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="cameraId">cbCameraId</param>
|
||||
|
/// <param name="streamLevel">码流级别[0,2], default:0</param>
|
||||
|
[HttpPost] |
||||
|
[ApiDescriptionSettings(Description = "cameraId:cbCameraId<br/>streamLevel:码流级别[0,2],default:0")]
|
||||
|
public async Task<object> 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); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 判断直播流是否在线
|
||||
|
/// </summary>
|
||||
|
/// <param name="cameraId">cbCameraId</param>
|
||||
|
/// <param name="streamLevel">码流级别[0,2], default:0</param>
|
||||
|
[HttpPost] |
||||
|
[ApiDescriptionSettings(Description = "cameraId:cbCameraId<br/>streamLevel:码流级别[0,2],default:0")]
|
||||
|
public async Task<bool> 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
|
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
namespace Cis.Core; |
||||
|
|
||||
|
public class ZLMediaKitOptions : IConfigurableOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 服务IP
|
||||
|
/// </summary>
|
||||
|
public string Ip { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 服务端口
|
||||
|
/// </summary>
|
||||
|
public string Port { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 服务鉴权秘钥
|
||||
|
/// </summary>
|
||||
|
public string Secret { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 默认虚拟主机
|
||||
|
/// </summary>
|
||||
|
public string DefaultVhost { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 默认应用名
|
||||
|
/// </summary>
|
||||
|
public string DefaultApp { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// rtsp拉流时,拉流方式
|
||||
|
/// </summary>
|
||||
|
public string RtpType { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 拉流超时时间,单位秒,float类型
|
||||
|
/// </summary>
|
||||
|
public float TimeoutSec { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 拉流重试次数
|
||||
|
/// </summary>
|
||||
|
public int RetryCount { get; set; } |
||||
|
} |
Loading…
Reference in new issue