You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Java搭建持续向网页推送视频二进制数据的Web服务

核心问题定位

你的代码目前存在两个根本性错误导致无法正常播放:

  • 数据格式不匹配:你返回的是OpenCV Mat的原始像素数组,既不是JPG/PNG这类图片编码格式,也不是H264这类视频编码格式,<video>标签无法直接解析原始像素数据,你手动指定的type: "video/H264"和实际返回的内容完全不符。
  • 服务端逻辑错误:普通Spring MVC接口的return语句会直接结束当前HTTP请求,你写的while循环只会执行一次就终止,永远只能返回第一帧数据,不可能实现持续流传输。

疑问解答

你提到的两种拉流模式都有成熟的实现方案,对应不同的技术选型:

方案1:客户端持续发请求拉取单帧(MJPG短轮询,实现最简单)

这是入门级实时流的最优选择,不需要使用<video>标签,直接用<img>标签即可实现,开发成本极低:

  • 服务端改造:删除原来的while循环,每次请求返回当前最新一帧的JPG编码数据即可,核心代码修改如下:
    import org.springframework.http.MediaType;
    import org.opencv.imgcodecs.Imgcodecs;
    import org.opencv.core.MatOfByte;
    
    @RestController
    public class GreetingController {
        // 原有成员变量和构造方法保持不变
        
        @GetMapping(value = "/streaming", produces = MediaType.IMAGE_JPEG_VALUE)
        public byte[] streaming() {
            if (videoCapture.isOpened() && videoCapture.read(m)) {
                MatOfByte jpgBuffer = new MatOfByte();
                // 将Mat直接编码为JPG格式字节数组
                Imgcodecs.imencode(".jpg", m, jpgBuffer);
                return jpgBuffer.toArray();
            }
            return new byte[0];
        }
    }
    
  • 客户端改造:不需要AJAX,直接通过定时器刷新<img>标签的src地址即可,代码如下:
    <!DOCTYPE html>
    <html>
    <head>
        <title>实时流</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <img id="videoFrame" alt="实时画面">
    </body>
    <script>
        const img = document.getElementById('videoFrame');
        // 每40ms刷新一次,对应25帧/秒的帧率
        setInterval(() => {
            // 加时间戳避免浏览器缓存图片
            img.src = 'http://localhost:8080/streaming?' + Date.now();
        }, 40);
    </script>
    </html>
    

方案2:客户端仅发一次请求,服务端持续返回流(H264长连接方案)

如果你需要用<video>标签实现更低带宽的传输,就选择这种方案,需要改造服务端为长连接响应模式:

  • 服务端改造:不能用普通接口的return返回数据,要使用StreamingResponseBody保持HTTP长连接,循环编码H264帧写入响应流,不会主动断开连接,核心代码示例:
    import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
    
    @GetMapping("/streaming")
    public StreamingResponseBody streaming() {
        return outputStream -> {
            MatOfByte frameBuffer = new MatOfByte();
            while (videoCapture.isOpened() && videoCapture.read(m)) {
                // 这里需要将Mat编码为H264格式的切片,推荐集成FFmpeg实现编码
                // 测试阶段可以先用JPG格式验证流传输逻辑
                Imgcodecs.imencode(".jpg", m, frameBuffer);
                byte[] frameBytes = frameBuffer.toArray();
                // 写入帧长度标识,方便前端切分不同的帧
                outputStream.write(intToBytes(frameBytes.length));
                // 写入帧数据
                outputStream.write(frameBytes);
                outputStream.flush();
                // 控制帧率为25帧/秒
                Thread.sleep(40);
            }
        };
    }
    
    // 辅助方法:int转4字节数组
    private byte[] intToBytes(int value) {
        return new byte[] {
            (byte)(value >> 24),
            (byte)(value >> 16),
            (byte)(value >> 8),
            (byte)value
        };
    }
    
  • 客户端改造:不能用普通AJAX一次性拉取数据,要使用Fetch API的可读流持续读取响应,切分帧后喂给MediaSource对象,再绑定到<video>标签播放。

实现建议

优先选择方案1实现,不需要处理复杂的视频编码逻辑,代码改动最小,延迟也能满足大多数普通场景的需求。跑通基础功能后,如果需要降低带宽占用,再升级为方案2的H264流方案即可。

内容的提问来源于stack exchange,提问作者O Connor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 01:24:05