diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java index 43cc48997..6cb023357 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java @@ -91,14 +91,23 @@ public class AiRoiController { @Operation(summary = "获取摄像头截图") @GetMapping("/snap") public void getSnap(HttpServletResponse resp, - @RequestParam String url) { + @RequestParam String app, + @RequestParam String stream) { MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); if (mediaServer == null) { log.warn("[AI截图] 无可用媒体服务器"); resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); return; } - // 直接调用ZLM的getSnap同步接口,传入RTSP源地址 + // 使用ZLM内部RTSP流地址截图(流必须在线) + String internalUrl; + if (mediaServer.getRtspPort() != 0) { + internalUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServer.getRtspPort(), app, stream); + } else { + internalUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServer.getHttpPort(), app, stream); + } + log.info("[AI截图] app={}, stream={}, 内部地址={}", app, stream, internalUrl); + String zlmApi = String.format("http://%s:%s/index/api/getSnap", mediaServer.getIp(), mediaServer.getHttpPort()); HttpUrl httpUrl = HttpUrl.parse(zlmApi); @@ -108,14 +117,14 @@ public class AiRoiController { } HttpUrl requestUrl = httpUrl.newBuilder() .addQueryParameter("secret", mediaServer.getSecret()) - .addQueryParameter("url", url) - .addQueryParameter("timeout_sec", "15") + .addQueryParameter("url", internalUrl) + .addQueryParameter("timeout_sec", "10") .addQueryParameter("expire_sec", "1") .build(); OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) - .readTimeout(20, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) .build(); try { @@ -123,14 +132,14 @@ public class AiRoiController { Response response = client.newCall(request).execute(); if (response.isSuccessful() && response.body() != null) { String contentType = response.header("Content-Type", ""); - if (contentType.contains("image")) { + byte[] bytes = response.body().bytes(); + // ZLM默认logo是47255字节,跳过它 + if (contentType.contains("image") && bytes.length != 47255) { resp.setContentType(MediaType.IMAGE_JPEG_VALUE); - byte[] bytes = response.body().bytes(); resp.getOutputStream().write(bytes); resp.getOutputStream().flush(); } else { - // ZLM返回的是JSON错误信息,不是图片 - log.warn("[AI截图] ZLM未返回图片,可能流未在线: {}", response.body().string()); + log.warn("[AI截图] 截图返回默认logo或非图片,流可能未在线,size={}", bytes.length); resp.setStatus(HttpServletResponse.SC_NO_CONTENT); } } else { diff --git a/web/src/api/cameraConfig.js b/web/src/api/cameraConfig.js index cf1021304..99ab373d9 100644 --- a/web/src/api/cameraConfig.js +++ b/web/src/api/cameraConfig.js @@ -25,6 +25,7 @@ export function stopCamera(id) { }) } -export function getSnapUrl(srcUrl) { - return `/api/ai/roi/snap?url=${encodeURIComponent(srcUrl)}` +export function getSnapUrl(app, stream) { + const base = process.env.NODE_ENV === 'development' ? process.env.VUE_APP_BASE_API : '' + return `${base}/api/ai/roi/snap?app=${encodeURIComponent(app)}&stream=${encodeURIComponent(stream)}` } diff --git a/web/src/views/cameraConfig/roiConfig.vue b/web/src/views/cameraConfig/roiConfig.vue index 0dab22cfc..c4b978345 100644 --- a/web/src/views/cameraConfig/roiConfig.vue +++ b/web/src/views/cameraConfig/roiConfig.vue @@ -97,6 +97,8 @@ export default { return { cameraId: '', srcUrl: '', + app: '', + stream: '', drawMode: null, roiList: [], selectedRoiId: null, @@ -113,10 +115,9 @@ export default { mounted() { this.cameraId = decodeURIComponent(this.$route.params.cameraId) this.srcUrl = this.$route.query.srcUrl || '' - if (this.srcUrl) { - const base = process.env.NODE_ENV === 'development' ? process.env.VUE_APP_BASE_API : '' - this.snapUrl = `${base}/api/ai/roi/snap?url=${encodeURIComponent(this.srcUrl)}` - } + this.app = this.$route.query.app || '' + this.stream = this.$route.query.stream || '' + this.buildSnapUrl() this.loadRois() }, methods: { @@ -145,12 +146,15 @@ export default { startDraw(mode) { this.drawMode = mode }, - refreshSnap() { - if (this.srcUrl) { + buildSnapUrl() { + if (this.app && this.stream) { const base = process.env.NODE_ENV === 'development' ? process.env.VUE_APP_BASE_API : '' - this.snapUrl = `${base}/api/ai/roi/snap?url=${encodeURIComponent(this.srcUrl)}&t=${Date.now()}` + this.snapUrl = `${base}/api/ai/roi/snap?app=${encodeURIComponent(this.app)}&stream=${encodeURIComponent(this.stream)}&t=${Date.now()}` } }, + refreshSnap() { + this.buildSnapUrl() + }, onRoiDrawn(data) { this.drawMode = null const newRoi = {