|
|
@ -119,7 +119,7 @@ public class HikNvrLiveStreamService { |
|
|
private String ffmpegPath; |
|
|
private String ffmpegPath; |
|
|
/** ZLMediaKit RTMP 基础地址。 */ |
|
|
/** ZLMediaKit RTMP 基础地址。 */ |
|
|
private String rtmpBaseUrl; |
|
|
private String rtmpBaseUrl; |
|
|
/** 返回给浏览器的 ZLMediaKit HTTP 基础地址。 */ |
|
|
|
|
|
|
|
|
/** 固定的 ZLMediaKit HTTP 基础地址,作为请求无法提供对外地址时的回退值。 */ |
|
|
private String httpBaseUrl; |
|
|
private String httpBaseUrl; |
|
|
/** 服务端访问 ZLMediaKit API 的 HTTP 基础地址。 */ |
|
|
/** 服务端访问 ZLMediaKit API 的 HTTP 基础地址。 */ |
|
|
private String zlmApiBaseUrl; |
|
|
private String zlmApiBaseUrl; |
|
|
@ -227,9 +227,11 @@ public class HikNvrLiveStreamService { |
|
|
* |
|
|
* |
|
|
* @param cameraCode 页面设备树提供的 IVS 完整摄像机编码 |
|
|
* @param cameraCode 页面设备树提供的 IVS 完整摄像机编码 |
|
|
* @param streamType 主码流 1 或子码流 2 |
|
|
* @param streamType 主码流 1 或子码流 2 |
|
|
|
|
|
* @param publicBaseUrl 本次请求解析出的浏览器可达基础地址;为空时回退固定配置 |
|
|
* @return 已播放、正在排队或正在启动的实时流状态 |
|
|
* @return 已播放、正在排队或正在启动的实时流状态 |
|
|
*/ |
|
|
*/ |
|
|
public HikNvrLiveStreamResponse start(String cameraCode, Integer streamType) { |
|
|
|
|
|
|
|
|
public HikNvrLiveStreamResponse start( |
|
|
|
|
|
String cameraCode, Integer streamType, String publicBaseUrl) { |
|
|
// cameraCode 是实时预览唯一设备标识,NVR 与通道由服务端数据库解析。 |
|
|
// cameraCode 是实时预览唯一设备标识,NVR 与通道由服务端数据库解析。 |
|
|
String targetCameraCode = requireCameraCode(cameraCode); |
|
|
String targetCameraCode = requireCameraCode(cameraCode); |
|
|
// 启动前校验 FFmpeg、ZLM API 和媒体网关配置,缺项时立即返回明确错误。 |
|
|
// 启动前校验 FFmpeg、ZLM API 和媒体网关配置,缺项时立即返回明确错误。 |
|
|
@ -253,8 +255,7 @@ public class HikNvrLiveStreamService { |
|
|
|
|
|
|
|
|
String sessionKey = sessionKey(targetCameraCode); |
|
|
String sessionKey = sessionKey(targetCameraCode); |
|
|
String streamId = buildStreamId(targetCameraCode); |
|
|
String streamId = buildStreamId(targetCameraCode); |
|
|
PendingStart pending = new PendingStart( |
|
|
|
|
|
target, type, streamId, hlsUrl(streamId), flvUrl(streamId)); |
|
|
|
|
|
|
|
|
PendingStart pending = new PendingStart(target, type, streamId, publicBaseUrl); |
|
|
|
|
|
|
|
|
// 相同摄像机已有任务时复用;码流切换则取消旧任务并用新任务替换。 |
|
|
// 相同摄像机已有任务时复用;码流切换则取消旧任务并用新任务替换。 |
|
|
while (true) { |
|
|
while (true) { |
|
|
@ -265,7 +266,8 @@ public class HikNvrLiveStreamService { |
|
|
} |
|
|
} |
|
|
// 相同码流正在排队或启动时直接返回现有任务,避免重复拉流。 |
|
|
// 相同码流正在排队或启动时直接返回现有任务,避免重复拉流。 |
|
|
if (previous.streamType == type) { |
|
|
if (previous.streamType == type) { |
|
|
return toPendingResponse(previous, "实时预览正在排队或启动"); |
|
|
|
|
|
|
|
|
return toPendingResponse( |
|
|
|
|
|
previous, "实时预览正在排队或启动", publicBaseUrl); |
|
|
} |
|
|
} |
|
|
// 码流类型变化时取消旧任务,旧任务会在下一探测点释放已启动的 FFmpeg。 |
|
|
// 码流类型变化时取消旧任务,旧任务会在下一探测点释放已启动的 FFmpeg。 |
|
|
previous.cancelled = true; |
|
|
previous.cancelled = true; |
|
|
@ -303,7 +305,7 @@ public class HikNvrLiveStreamService { |
|
|
|
|
|
|
|
|
// 非首个任务已安全进入队列,立即返回 STARTING 让前端轮询状态。 |
|
|
// 非首个任务已安全进入队列,立即返回 STARTING 让前端轮询状态。 |
|
|
if (!firstInLane) { |
|
|
if (!firstInLane) { |
|
|
return toPendingResponse(pending, "实时预览排队中"); |
|
|
|
|
|
|
|
|
return toPendingResponse(pending, "实时预览排队中", publicBaseUrl); |
|
|
} |
|
|
} |
|
|
try { |
|
|
try { |
|
|
// 首个任务最多等待总启动时限加少量调度余量,避免 Tomcat 请求长时间挂死。 |
|
|
// 首个任务最多等待总启动时限加少量调度余量,避免 Tomcat 请求长时间挂死。 |
|
|
@ -311,11 +313,11 @@ public class HikNvrLiveStreamService { |
|
|
startupTimeoutMillis + 500L, TimeUnit.MILLISECONDS); |
|
|
startupTimeoutMillis + 500L, TimeUnit.MILLISECONDS); |
|
|
} catch (TimeoutException timeout) { |
|
|
} catch (TimeoutException timeout) { |
|
|
// 原生 SDK 若超出预期仍在工作,HTTP 先返回 STARTING,后台结果由状态接口提供。 |
|
|
// 原生 SDK 若超出预期仍在工作,HTTP 先返回 STARTING,后台结果由状态接口提供。 |
|
|
return toPendingResponse(pending, "实时预览启动中"); |
|
|
|
|
|
|
|
|
return toPendingResponse(pending, "实时预览启动中", publicBaseUrl); |
|
|
} catch (InterruptedException interrupted) { |
|
|
} catch (InterruptedException interrupted) { |
|
|
// 请求线程被中断时保留后台任务,并恢复线程中断标志。 |
|
|
// 请求线程被中断时保留后台任务,并恢复线程中断标志。 |
|
|
Thread.currentThread().interrupt(); |
|
|
Thread.currentThread().interrupt(); |
|
|
return toPendingResponse(pending, "实时预览启动中"); |
|
|
|
|
|
|
|
|
return toPendingResponse(pending, "实时预览启动中", publicBaseUrl); |
|
|
} catch (ExecutionException execution) { |
|
|
} catch (ExecutionException execution) { |
|
|
Throwable cause = execution.getCause(); |
|
|
Throwable cause = execution.getCause(); |
|
|
// 起流任务已返回统一业务异常时保持原始字符串错误码和 HTTP 状态。 |
|
|
// 起流任务已返回统一业务异常时保持原始字符串错误码和 HTTP 状态。 |
|
|
@ -395,7 +397,7 @@ public class HikNvrLiveStreamService { |
|
|
&& existing.isAlive() |
|
|
&& existing.isAlive() |
|
|
&& existing.streamType == pending.streamType |
|
|
&& existing.streamType == pending.streamType |
|
|
&& queryZlmStream(existing.streamId) == ZlmProbeResult.REGISTERED) { |
|
|
&& queryZlmStream(existing.streamId) == ZlmProbeResult.REGISTERED) { |
|
|
return toResponse(existing, "PLAYING", "实时预览已在播放"); |
|
|
|
|
|
|
|
|
return toResponse(existing, "PLAYING", "实时预览已在播放", pending.publicBaseUrl); |
|
|
} |
|
|
} |
|
|
// 旧进程已退出或码流类型变化时先释放旧流,再启动本次请求。 |
|
|
// 旧进程已退出或码流类型变化时先释放旧流,再启动本次请求。 |
|
|
if (existing != null) { |
|
|
if (existing != null) { |
|
|
@ -520,9 +522,7 @@ public class HikNvrLiveStreamService { |
|
|
pending.target.getChannel(), |
|
|
pending.target.getChannel(), |
|
|
pending.streamType, |
|
|
pending.streamType, |
|
|
pending.streamId, |
|
|
pending.streamId, |
|
|
process, |
|
|
|
|
|
pending.hlsUrl, |
|
|
|
|
|
pending.flvUrl); |
|
|
|
|
|
|
|
|
process); |
|
|
// 注册本地会话后,停止和状态请求即可立即定位并释放正在启动的进程。 |
|
|
// 注册本地会话后,停止和状态请求即可立即定位并释放正在启动的进程。 |
|
|
sessions.put(sessionKey, session); |
|
|
sessions.put(sessionKey, session); |
|
|
log.info("[live] started cameraCode={} nvrIp={} host={} cameraChannel={} source={} streamType={} streamId={} attempt={} log={}", |
|
|
log.info("[live] started cameraCode={} nvrIp={} host={} cameraChannel={} source={} streamType={} streamId={} attempt={} log={}", |
|
|
@ -541,7 +541,7 @@ public class HikNvrLiveStreamService { |
|
|
// ZLM 已确认同名 RTMP 流注册时才认为起流成功。 |
|
|
// ZLM 已确认同名 RTMP 流注册时才认为起流成功。 |
|
|
if (probe == ZlmProbeResult.REGISTERED) { |
|
|
if (probe == ZlmProbeResult.REGISTERED) { |
|
|
failedStarts.remove(sessionKey); |
|
|
failedStarts.remove(sessionKey); |
|
|
return toResponse(session, "PLAYING", "实时预览已启动"); |
|
|
|
|
|
|
|
|
return toResponse(session, "PLAYING", "实时预览已启动", pending.publicBaseUrl); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 未注册或进程退出时立即销毁 FFmpeg,快速断开 NVR RTSP 会话。 |
|
|
// 未注册或进程退出时立即销毁 FFmpeg,快速断开 NVR RTSP 会话。 |
|
|
@ -644,9 +644,10 @@ public class HikNvrLiveStreamService { |
|
|
* 查询指定 cameraCode 当前排队、播放或失败状态。 |
|
|
* 查询指定 cameraCode 当前排队、播放或失败状态。 |
|
|
* |
|
|
* |
|
|
* @param cameraCode 页面设备树提供的 IVS 完整摄像机编码 |
|
|
* @param cameraCode 页面设备树提供的 IVS 完整摄像机编码 |
|
|
|
|
|
* @param publicBaseUrl 本次请求解析出的浏览器可达基础地址;为空时回退固定配置 |
|
|
* @return 实时流状态 |
|
|
* @return 实时流状态 |
|
|
*/ |
|
|
*/ |
|
|
public HikNvrLiveStreamResponse status(String cameraCode) { |
|
|
|
|
|
|
|
|
public HikNvrLiveStreamResponse status(String cameraCode, String publicBaseUrl) { |
|
|
// 规范化 cameraCode,确保状态查询读取唯一摄像机会话。 |
|
|
// 规范化 cameraCode,确保状态查询读取唯一摄像机会话。 |
|
|
String targetCameraCode = requireCameraCode(cameraCode); |
|
|
String targetCameraCode = requireCameraCode(cameraCode); |
|
|
String sessionKey = sessionKey(targetCameraCode); |
|
|
String sessionKey = sessionKey(targetCameraCode); |
|
|
@ -656,14 +657,14 @@ public class HikNvrLiveStreamService { |
|
|
ZlmProbeResult probe = queryZlmStream(session.streamId); |
|
|
ZlmProbeResult probe = queryZlmStream(session.streamId); |
|
|
// ZLM 已注册说明前端现在可以安全拉取 FLV/HLS。 |
|
|
// ZLM 已注册说明前端现在可以安全拉取 FLV/HLS。 |
|
|
if (probe == ZlmProbeResult.REGISTERED) { |
|
|
if (probe == ZlmProbeResult.REGISTERED) { |
|
|
return toResponse(session, "PLAYING", "实时预览播放中"); |
|
|
|
|
|
|
|
|
return toResponse(session, "PLAYING", "实时预览播放中", publicBaseUrl); |
|
|
} |
|
|
} |
|
|
// 进程仍在时统一返回 STARTING;即使 ZLM API 短暂不可用,也不能误报为 STOPPED。 |
|
|
// 进程仍在时统一返回 STARTING;即使 ZLM API 短暂不可用,也不能误报为 STOPPED。 |
|
|
if (session.isAlive()) { |
|
|
if (session.isAlive()) { |
|
|
String message = probe == ZlmProbeResult.UNAVAILABLE |
|
|
String message = probe == ZlmProbeResult.UNAVAILABLE |
|
|
? "ZLMediaKit状态暂时无法确认" |
|
|
? "ZLMediaKit状态暂时无法确认" |
|
|
: "实时预览启动中"; |
|
|
: "实时预览启动中"; |
|
|
return toResponse(session, "STARTING", message); |
|
|
|
|
|
|
|
|
return toResponse(session, "STARTING", message, publicBaseUrl); |
|
|
} |
|
|
} |
|
|
// 进程已经退出时移除旧映射,后面返回异步失败或停止状态。 |
|
|
// 进程已经退出时移除旧映射,后面返回异步失败或停止状态。 |
|
|
if (!session.isAlive()) { |
|
|
if (!session.isAlive()) { |
|
|
@ -674,7 +675,7 @@ public class HikNvrLiveStreamService { |
|
|
PendingStart pending = pendingStarts.get(sessionKey); |
|
|
PendingStart pending = pendingStarts.get(sessionKey); |
|
|
// 排队任务尚未产生进程时向前端返回地址和 STARTING,供统一轮询。 |
|
|
// 排队任务尚未产生进程时向前端返回地址和 STARTING,供统一轮询。 |
|
|
if (pending != null) { |
|
|
if (pending != null) { |
|
|
return toPendingResponse(pending, "实时预览排队中"); |
|
|
|
|
|
|
|
|
return toPendingResponse(pending, "实时预览排队中", publicBaseUrl); |
|
|
} |
|
|
} |
|
|
HikNvrLiveStreamResponse failure = failedStarts.get(sessionKey); |
|
|
HikNvrLiveStreamResponse failure = failedStarts.get(sessionKey); |
|
|
// 异步任务已经失败时返回稳定错误码和具体消息,避免前端只显示通用超时。 |
|
|
// 异步任务已经失败时返回稳定错误码和具体消息,避免前端只显示通用超时。 |
|
|
@ -685,12 +686,13 @@ public class HikNvrLiveStreamService { |
|
|
String streamId = buildStreamId(targetCameraCode); |
|
|
String streamId = buildStreamId(targetCameraCode); |
|
|
// 本地状态丢失但 ZLM 仍有同名流时覆盖容器重启或其他入口创建的流复用场景。 |
|
|
// 本地状态丢失但 ZLM 仍有同名流时覆盖容器重启或其他入口创建的流复用场景。 |
|
|
if (queryZlmStream(streamId) == ZlmProbeResult.REGISTERED) { |
|
|
if (queryZlmStream(streamId) == ZlmProbeResult.REGISTERED) { |
|
|
|
|
|
String baseUrl = browserBaseUrl(publicBaseUrl, null); |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
.cameraCode(targetCameraCode) |
|
|
.cameraCode(targetCameraCode) |
|
|
.streamId(streamId) |
|
|
.streamId(streamId) |
|
|
.status("PLAYING") |
|
|
.status("PLAYING") |
|
|
.hlsUrl(hlsUrl(streamId)) |
|
|
|
|
|
.flvUrl(flvUrl(streamId)) |
|
|
|
|
|
|
|
|
.hlsUrl(hlsUrl(streamId, baseUrl)) |
|
|
|
|
|
.flvUrl(flvUrl(streamId, baseUrl)) |
|
|
.message("ZLMediaKit中已有同名实时流") |
|
|
.message("ZLMediaKit中已有同名实时流") |
|
|
.build(); |
|
|
.build(); |
|
|
} |
|
|
} |
|
|
@ -1748,11 +1750,17 @@ public class HikNvrLiveStreamService { |
|
|
return builder.start(); |
|
|
return builder.start(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** 将本地会话转换为接口响应。 */ |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 将本地会话转换为接口响应。 |
|
|
|
|
|
* |
|
|
|
|
|
* @param publicBaseUrl 当前请求解析出的对外基础地址;为空时回退固定配置 |
|
|
|
|
|
*/ |
|
|
private HikNvrLiveStreamResponse toResponse( |
|
|
private HikNvrLiveStreamResponse toResponse( |
|
|
LiveSession session, |
|
|
LiveSession session, |
|
|
String status, |
|
|
String status, |
|
|
String message) { |
|
|
|
|
|
|
|
|
String message, |
|
|
|
|
|
String publicBaseUrl) { |
|
|
|
|
|
String baseUrl = browserBaseUrl(publicBaseUrl, null); |
|
|
// 返回服务端解析出的 NVR 和通道,但绝不回传账号密码或 RTSP 地址。 |
|
|
// 返回服务端解析出的 NVR 和通道,但绝不回传账号密码或 RTSP 地址。 |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
.cameraCode(session.cameraCode) |
|
|
.cameraCode(session.cameraCode) |
|
|
@ -1761,16 +1769,22 @@ public class HikNvrLiveStreamService { |
|
|
.streamType(session.streamType) |
|
|
.streamType(session.streamType) |
|
|
.streamId(session.streamId) |
|
|
.streamId(session.streamId) |
|
|
.status(status) |
|
|
.status(status) |
|
|
.hlsUrl(session.hlsUrl) |
|
|
|
|
|
.flvUrl(session.flvUrl) |
|
|
|
|
|
|
|
|
.hlsUrl(hlsUrl(session.streamId, baseUrl)) |
|
|
|
|
|
.flvUrl(flvUrl(session.streamId, baseUrl)) |
|
|
.message(message) |
|
|
.message(message) |
|
|
.build(); |
|
|
.build(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** 将排队任务转换为 STARTING 响应。 */ |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 将排队任务转换为 STARTING 响应。 |
|
|
|
|
|
* |
|
|
|
|
|
* @param publicBaseUrl 当前请求解析出的对外基础地址;为空时沿用创建任务时的地址 |
|
|
|
|
|
*/ |
|
|
private HikNvrLiveStreamResponse toPendingResponse( |
|
|
private HikNvrLiveStreamResponse toPendingResponse( |
|
|
PendingStart pending, |
|
|
PendingStart pending, |
|
|
String message) { |
|
|
|
|
|
|
|
|
String message, |
|
|
|
|
|
String publicBaseUrl) { |
|
|
|
|
|
String baseUrl = browserBaseUrl(publicBaseUrl, pending.publicBaseUrl); |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
.cameraCode(pending.target.getCameraCode()) |
|
|
.cameraCode(pending.target.getCameraCode()) |
|
|
.nvrIp(pending.target.getNvrInfo().getNvrIp()) |
|
|
.nvrIp(pending.target.getNvrInfo().getNvrIp()) |
|
|
@ -1778,8 +1792,8 @@ public class HikNvrLiveStreamService { |
|
|
.streamType(pending.streamType) |
|
|
.streamType(pending.streamType) |
|
|
.streamId(pending.streamId) |
|
|
.streamId(pending.streamId) |
|
|
.status("STARTING") |
|
|
.status("STARTING") |
|
|
.hlsUrl(pending.hlsUrl) |
|
|
|
|
|
.flvUrl(pending.flvUrl) |
|
|
|
|
|
|
|
|
.hlsUrl(hlsUrl(pending.streamId, baseUrl)) |
|
|
|
|
|
.flvUrl(flvUrl(pending.streamId, baseUrl)) |
|
|
.message(message) |
|
|
.message(message) |
|
|
.build(); |
|
|
.build(); |
|
|
} |
|
|
} |
|
|
@ -1821,6 +1835,7 @@ public class HikNvrLiveStreamService { |
|
|
PendingStart pending, |
|
|
PendingStart pending, |
|
|
String status, |
|
|
String status, |
|
|
String message) { |
|
|
String message) { |
|
|
|
|
|
String baseUrl = browserBaseUrl(pending.publicBaseUrl, null); |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
return HikNvrLiveStreamResponse.builder() |
|
|
.cameraCode(pending.target.getCameraCode()) |
|
|
.cameraCode(pending.target.getCameraCode()) |
|
|
.nvrIp(pending.target.getNvrInfo().getNvrIp()) |
|
|
.nvrIp(pending.target.getNvrInfo().getNvrIp()) |
|
|
@ -1828,8 +1843,8 @@ public class HikNvrLiveStreamService { |
|
|
.streamType(pending.streamType) |
|
|
.streamType(pending.streamType) |
|
|
.streamId(pending.streamId) |
|
|
.streamId(pending.streamId) |
|
|
.status(status) |
|
|
.status(status) |
|
|
.hlsUrl(pending.hlsUrl) |
|
|
|
|
|
.flvUrl(pending.flvUrl) |
|
|
|
|
|
|
|
|
.hlsUrl(hlsUrl(pending.streamId, baseUrl)) |
|
|
|
|
|
.flvUrl(flvUrl(pending.streamId, baseUrl)) |
|
|
.message(message) |
|
|
.message(message) |
|
|
.build(); |
|
|
.build(); |
|
|
} |
|
|
} |
|
|
@ -1856,13 +1871,33 @@ public class HikNvrLiveStreamService { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** 返回当前流的 HLS 浏览器地址。 */ |
|
|
/** 返回当前流的 HLS 浏览器地址。 */ |
|
|
private String hlsUrl(String streamId) { |
|
|
|
|
|
return httpBaseUrl + "/live/" + streamId + "/hls.m3u8"; |
|
|
|
|
|
|
|
|
private String hlsUrl(String streamId, String baseUrl) { |
|
|
|
|
|
return baseUrl + "/live/" + streamId + "/hls.m3u8"; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** 返回当前流的 HTTP-FLV 浏览器地址。 */ |
|
|
/** 返回当前流的 HTTP-FLV 浏览器地址。 */ |
|
|
private String flvUrl(String streamId) { |
|
|
|
|
|
return httpBaseUrl + "/live/" + streamId + ".live.flv"; |
|
|
|
|
|
|
|
|
private String flvUrl(String streamId, String baseUrl) { |
|
|
|
|
|
return baseUrl + "/live/" + streamId + ".live.flv"; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 选取返回给浏览器的 ZLMediaKit 基础地址。 |
|
|
|
|
|
* |
|
|
|
|
|
* <p>内网/外网两个 IP 共用一套服务时,播放地址必须跟随本次请求的来源地址: |
|
|
|
|
|
* 请求解析出的地址优先,其次是创建排队任务时记录的地址,都没有时回退固定配置。</p> |
|
|
|
|
|
* |
|
|
|
|
|
* @param requestBaseUrl 当前请求解析出的地址,可为空 |
|
|
|
|
|
* @param taskBaseUrl 创建排队任务时记录的地址,可为空 |
|
|
|
|
|
* @return 已去除末尾斜杠的基础地址 |
|
|
|
|
|
*/ |
|
|
|
|
|
private String browserBaseUrl(String requestBaseUrl, String taskBaseUrl) { |
|
|
|
|
|
if (hasText(requestBaseUrl)) { |
|
|
|
|
|
return trimTrailingSlash(requestBaseUrl.trim()); |
|
|
|
|
|
} |
|
|
|
|
|
if (hasText(taskBaseUrl)) { |
|
|
|
|
|
return trimTrailingSlash(taskBaseUrl.trim()); |
|
|
|
|
|
} |
|
|
|
|
|
return httpBaseUrl; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** 返回当前流保存原始 FFmpeg stderr 的日志文件。 */ |
|
|
/** 返回当前流保存原始 FFmpeg stderr 的日志文件。 */ |
|
|
@ -1879,7 +1914,7 @@ public class HikNvrLiveStreamService { |
|
|
ZLM_PUSH_FAILED, |
|
|
ZLM_PUSH_FAILED, |
|
|
ZLM_PUSH_FAILED + ":未找到FFmpeg:" + ffmpegPath); |
|
|
ZLM_PUSH_FAILED + ":未找到FFmpeg:" + ffmpegPath); |
|
|
} |
|
|
} |
|
|
// RTMP、浏览器地址、API 地址和 secret 任一缺失都无法完成可靠就绪确认。 |
|
|
|
|
|
|
|
|
// RTMP、回退播放地址、API 地址和 secret 任一缺失都无法完成可靠就绪确认。 |
|
|
if (!hasText(rtmpBaseUrl) |
|
|
if (!hasText(rtmpBaseUrl) |
|
|
|| !hasText(httpBaseUrl) |
|
|
|| !hasText(httpBaseUrl) |
|
|
|| !hasText(zlmApiBaseUrl) |
|
|
|| !hasText(zlmApiBaseUrl) |
|
|
@ -1978,10 +2013,6 @@ public class HikNvrLiveStreamService { |
|
|
private final String streamId; |
|
|
private final String streamId; |
|
|
/** 持有 RTSP 会话的 FFmpeg 进程。 */ |
|
|
/** 持有 RTSP 会话的 FFmpeg 进程。 */ |
|
|
private final Process process; |
|
|
private final Process process; |
|
|
/** HLS 浏览器地址。 */ |
|
|
|
|
|
private final String hlsUrl; |
|
|
|
|
|
/** HTTP-FLV 浏览器地址。 */ |
|
|
|
|
|
private final String flvUrl; |
|
|
|
|
|
/** 连续无人观看的起始时间;0 表示当前有人观看或无法确认。 */ |
|
|
/** 连续无人观看的起始时间;0 表示当前有人观看或无法确认。 */ |
|
|
private volatile long idleSinceMillis; |
|
|
private volatile long idleSinceMillis; |
|
|
|
|
|
|
|
|
@ -1994,9 +2025,7 @@ public class HikNvrLiveStreamService { |
|
|
Integer channel, |
|
|
Integer channel, |
|
|
int streamType, |
|
|
int streamType, |
|
|
String streamId, |
|
|
String streamId, |
|
|
Process process, |
|
|
|
|
|
String hlsUrl, |
|
|
|
|
|
String flvUrl) { |
|
|
|
|
|
|
|
|
Process process) { |
|
|
this.cameraCode = cameraCode; |
|
|
this.cameraCode = cameraCode; |
|
|
this.nvrIp = nvrIp; |
|
|
this.nvrIp = nvrIp; |
|
|
this.sourceHost = sourceHost; |
|
|
this.sourceHost = sourceHost; |
|
|
@ -2005,8 +2034,6 @@ public class HikNvrLiveStreamService { |
|
|
this.streamType = streamType; |
|
|
this.streamType = streamType; |
|
|
this.streamId = streamId; |
|
|
this.streamId = streamId; |
|
|
this.process = process; |
|
|
this.process = process; |
|
|
this.hlsUrl = hlsUrl; |
|
|
|
|
|
this.flvUrl = flvUrl; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** 判断 FFmpeg 拉流进程是否仍在运行。 */ |
|
|
/** 判断 FFmpeg 拉流进程是否仍在运行。 */ |
|
|
@ -2033,28 +2060,24 @@ public class HikNvrLiveStreamService { |
|
|
private final int streamType; |
|
|
private final int streamType; |
|
|
/** ZLMediaKit 流标识。 */ |
|
|
/** ZLMediaKit 流标识。 */ |
|
|
private final String streamId; |
|
|
private final String streamId; |
|
|
/** HLS 浏览器地址。 */ |
|
|
|
|
|
private final String hlsUrl; |
|
|
|
|
|
/** HTTP-FLV 浏览器地址。 */ |
|
|
|
|
|
private final String flvUrl; |
|
|
|
|
|
|
|
|
/** 创建该任务时请求解析出的浏览器可达基础地址;为空时回退固定配置。 */ |
|
|
|
|
|
private final String publicBaseUrl; |
|
|
/** 首个请求等待结果,排队请求通过状态接口读取相同结果。 */ |
|
|
/** 首个请求等待结果,排队请求通过状态接口读取相同结果。 */ |
|
|
private final CompletableFuture<HikNvrLiveStreamResponse> future = |
|
|
private final CompletableFuture<HikNvrLiveStreamResponse> future = |
|
|
new CompletableFuture<>(); |
|
|
new CompletableFuture<>(); |
|
|
/** 停止或码流切换设置的取消标志。 */ |
|
|
/** 停止或码流切换设置的取消标志。 */ |
|
|
private volatile boolean cancelled; |
|
|
private volatile boolean cancelled; |
|
|
|
|
|
|
|
|
/** 保存起流目标和预先可返回给前端的流地址。 */ |
|
|
|
|
|
|
|
|
/** 保存起流目标和本次请求的播放地址基础前缀。 */ |
|
|
private PendingStart( |
|
|
private PendingStart( |
|
|
HikNvrCameraTarget target, |
|
|
HikNvrCameraTarget target, |
|
|
int streamType, |
|
|
int streamType, |
|
|
String streamId, |
|
|
String streamId, |
|
|
String hlsUrl, |
|
|
|
|
|
String flvUrl) { |
|
|
|
|
|
|
|
|
String publicBaseUrl) { |
|
|
this.target = target; |
|
|
this.target = target; |
|
|
this.streamType = streamType; |
|
|
this.streamType = streamType; |
|
|
this.streamId = streamId; |
|
|
this.streamId = streamId; |
|
|
this.hlsUrl = hlsUrl; |
|
|
|
|
|
this.flvUrl = flvUrl; |
|
|
|
|
|
|
|
|
this.publicBaseUrl = publicBaseUrl; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|