Skip to content

修复录像下载时 downLoadFilePath 为 null#2166

Open
liushuaiyu wants to merge 2 commits into
648540858:masterfrom
liushuaiyu:fix/download-filepath-null
Open

修复录像下载时 downLoadFilePath 为 null#2166
liushuaiyu wants to merge 2 commits into
648540858:masterfrom
liushuaiyu:fix/download-filepath-null

Conversation

@liushuaiyu

Copy link
Copy Markdown

问题原因与解决方案详细说明


一、问题根本原因

1. ZLM 事件发送顺序(硬编码)

当 NVR 停止推流时,ZLM 内部会按固定顺序触发两个 WebHook 事件:

顺序 事件 触发时机
1 on_stream_changed (regist=false) 流注销时
2 on_record_mp4 MP4 文件关闭完成后

关键问题on_stream_changed 总是先于 on_record_mp4 发送!

2. WVP 原有处理逻辑的缺陷

WVP 中有两个服务都监听了 MediaDepartureEvent(由 on_stream_changed 触发):

服务1:PlayServiceImpl

// 收到流注销事件后立即发送BYE并清理
if (MediaStreamUtil.isGB28181(event.getApp(), event.getStream())) {
    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(...);
    if (inviteInfo != null) {
        stop(inviteInfo);  // 立即停止,发送BYE
    }
}

服务2:InviteStreamServiceImpl

// 收到流注销事件后立即清理InviteInfo缓存
if ("rtsp".equals(event.getSchema()) && MediaStreamUtil.isGB28181(...)) {
    InviteInfo inviteInfo = getInviteInfoByStream(...);
    if (inviteInfo != null) {
        removeInviteInfo(inviteInfo);  // 立即清理
    }
}

3. 竞态条件导致的失败

时间线:
T0: NVR关闭RTP连接
T1: ZLM发送 on_stream_changed → WVP立即清理InviteInfo + 发送BYE
T2: ZLM发送 on_record_mp4 → WVP尝试获取InviteInfo → 失败(已被清理)
T3: 前端查询下载进度 → downLoadFilePath=null → 下载失败

二、解决方案

核心思路

对下载类型的会话延迟清理,确保 on_record_mp4 有足够时间处理并设置 downLoadFilePath

修改的文件

1. PlayServiceImpl.java(第239-253行)

if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) {
    log.info("[流离开] 下载会话延迟停止,等待on_record_mp4处理完成, stream={}", event.getStream());
    new Thread(() -> {
        try {
            Thread.sleep(5000);  // 延迟5秒
            InviteInfo inviteInfoDelay = inviteStreamService.getInviteInfoByStream(null, event.getStream());
            if (inviteInfoDelay != null && inviteInfoDelay.getType() == InviteSessionType.DOWNLOAD
                    && inviteInfoDelay.getStatus() == InviteSessionStatus.ok) {
                stop(inviteInfoDelay);
                log.info("[流离开] 下载会话延迟停止完成, stream={}", event.getStream());
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
} else {
    stop(inviteInfo);  // 非下载会话立即停止
}

2. InviteStreamServiceImpl.java(第56-69行)

if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) {
    log.info("[流离开] 下载会话延迟清理,等待on_record_mp4处理完成, stream={}", event.getStream());
    new Thread(() -> {
        try {
            Thread.sleep(5000);  // 延迟5秒
            InviteInfo inviteInfoDelay = getInviteInfoByStream(null, event.getStream());
            if (inviteInfoDelay != null && inviteInfoDelay.getType() == InviteSessionType.DOWNLOAD) {
                removeInviteInfo(inviteInfoDelay);
                log.info("[流离开] 下载会话延迟清理完成, stream={}", event.getStream());
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
}

三、修复后的执行流程

时间线:
T0: NVR关闭RTP连接
T1: ZLM发送 on_stream_changed → WVP启动延迟清理线程(5秒后执行)
T2: ZLM发送 on_record_mp4 → WVP成功获取InviteInfo → 设置downLoadFilePath ✅
T3: 前端查询下载进度 → downLoadFilePath有值 → 下载成功 ✅
T4: 5秒延迟到期 → WVP清理InviteInfo + 发送BYE

四、测试验证结果

从日志中可以看到修复后的正确流程:

15:07:45.398  on_record_mp4 被调用(录像完成)
15:07:45.420  InviteStreamServiceImpl: "[流离开] 下载会话延迟清理,等待on_record_mp4处理完成"
15:07:45.427  PlayServiceImpl: "[流离开] 下载会话延迟停止,等待on_record_mp4处理完成"
15:07:50.430  InviteStreamServiceImpl: "[流离开] 下载会话延迟清理完成"

最终结果

{
    "code": 0,
    "msg": "成功",
    "data": {
        "progress": 1.0,
        "downLoadFilePath": {
            "httpPath": "http://192.168.1.96:9092/index/api/downloadFile?file_path=..."
        }
    }
}

五、方案优势

特性 说明
最小侵入性 只修改下载类型会话,不影响点播、回放等其他功能
风险低 延迟清理在独立线程中执行,不会阻塞主线程
可配置延迟 5秒延迟可根据实际情况调整
已验证有效 经过多次测试确认修复成功

六、为什么需要两处修改?

PlayServiceImpl.stop() 方法内部也会调用 inviteStreamService.removeInviteInfo()

public void stop(InviteInfo inviteInfo) {
    // ...
    inviteStreamService.removeInviteInfo(inviteInfo);  // 也会清理
    // ...
}

如果只改一处:

  • 只改 PlayServiceImplInviteStreamServiceImpl 仍会立即清理
  • 只改 InviteStreamServiceImplPlayServiceImpl.stop() 仍会调用 removeInviteInfo()

因此必须两处都改才能真正实现延迟清理。

fix: 修复录像下载时 downLoadFilePath 为 null 的问题

当 ZLM 的 on_stream_changed 回调先于 on_record_mp4 到达时,
WVP 会立即清理会话,导致 on_record_mp4 处理时无法设置下载文件路径。

解决方案:
1. InviteStreamServiceImpl: 对下载类型的会话延迟5秒清理 InviteInfo
2. PlayServiceImpl: 对下载类型的会话延迟5秒发送BYE请求

这样确保 on_record_mp4 有足够时间处理并设置 downLoadFilePath。
fix: 修复录像下载时 downLoadFilePath 为 null 的问题

当 ZLM 的 on_stream_changed 回调先于 on_record_mp4 到达时,
WVP 会立即清理会话,导致 on_record_mp4 处理时无法设置下载文件路径。

解决方案:
1. InviteStreamServiceImpl: 对下载类型的会话延迟5秒清理 InviteInfo
2. PlayServiceImpl: 对下载类型的会话延迟5秒发送BYE请求

这样确保 on_record_mp4 有足够时间处理并设置 downLoadFilePath。
@vercel

vercel Bot commented Jun 2, 2026

Copy link
Copy Markdown

@liushuaiyu is attempting to deploy a commit to the 648540858's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant