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