http协议处理播放video/mp4视频

代码片段示例

复制代码
        String ossUrl = getVideoOssURL(taskCode,checkCode,fileName);
//        ossUrl = ossRoot+"/"+ossUrl;
        SimplifiedObjectMeta simplifiedObjectMeta = AliyunUtils.getSimplifiedObjectMeta(bucketName, ossUrl);
        long fileLength = simplifiedObjectMeta.getSize();
        String range = request.getHeader("Range");
        long start = 0, end = fileLength - 1;
        response.setContentType("video/mp4");
        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
        if (range != null) {
            String[] ranges = range.replace("bytes=", "").split("-");
            start = Long.parseLong(ranges[0]);
            if (ranges.length > 1 && !ranges[1].isEmpty()) {
                end = Long.parseLong(ranges[1]);
            } else {
                end = Math.min(fileLength - 1, start + (maxPartSize * 1024L * 1024L) - 1);
            }

            response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + fileLength);
            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(end - start + 1));
            response.setStatus(206);
        }else{
            //浏览器发出的探测包
            /*
              对于视频播放(尤其是 MP4/WebM 等 progressive streaming),浏览器第一个探测请求通常是 不带 Range 头 的 GET 请求(用来探活、获取文件信息)。
                HTTP/1.1 200 OK
                Content-Type: video/mp4          # 或者 video/webm
                Content-Length: 123456789        # 文件真实总大小(必须准确)
                Accept-Ranges: bytes             # ← 最重要!告诉浏览器支持 Range 请求
                Content-Disposition: inline      # 可选,推荐 inline
                Cache-Control: public, max-age=31536000
                ETag: "xxx"                      # 强烈推荐,加上 ETag
                Last-Modified: Wed, 08 May 2026 00:00:00 GMT
                必须返回 Accept-Ranges: bytes,否则浏览器后续不会发 Range 请求(或降级为全量下载)。
                Content-Length 必须是完整文件大小。
             */
            response.setStatus(200);
            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength));
        }

        // Set the content type and attachment header.
//            String contentType = request.getServletContext().getMimeType(video.getFile().getAbsolutePath());

//        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);


//            headers.setContentType(MediaType.parseMediaType(contentType));
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, ossUrl);
        // 对于大小为1000 Bytes的文件,正常的字节范围为0~999。
        // 获取0~999字节范围内的数据,包括0和999,共1000个字节的数据。如果指定的范围无效(比如开始或结束位置的指定值为负数,或指定值大于文件大小),则下载整个文件。
        getObjectRequest.setRange(start, end);
        // Create resource that represents the part of the video file.
        try (OSSObject ossObject = AliyunUtils.getOssObject(getObjectRequest);
                InputStream in = ossObject.getObjectContent();){
            ServletOutputStream out = response.getOutputStream();
            byte[] buf = new byte[8192];
            for (int n = 0; n != -1; ) {
                n = in.read(buf, 0, buf.length);
                out.write(buf, 0, n);
            }
        } catch (ClientAbortException e) {
            // 【关键修复】客户端主动断开属于正常现象,不要抛异常
            log.info("视频流被客户端中断(正常现象): {}", e.getMessage());
            // 不要继续写数据,也不要抛异常
        } catch (Exception e) {
            log.error("视频流处理异常", e);
            throw new RuntimeException(e); // 其他真实异常才往上抛
        }

视频流接口异常处理

这是一个典型的视频流接口异常处理不当导致的连锁问题:

核心错误链

  1. videoPlayRangeOss 方法在往客户端写视频数据时,客户端突然断开连接(Broken pipe)。
  2. 这触发了 ClientAbortException。
  3. 全局异常处理器(ControllerAdviceConfig#runTimeExceptionHandler)试图返回 BaseResponse(JSON)。
  4. 但此时响应头已经是 video/mp4,Spring 找不到对应的 HttpMessageConverter,抛出 HttpMessageNotWritableException。

最终解决方案(推荐这样改)

1. 修改全局异常处理器(最关键)

Java

复制代码
@ControllerAdvice
public class ControllerAdviceConfig {

    private static final Logger log = LoggerFactory.getLogger(ControllerAdviceConfig.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> runTimeExceptionHandler(HttpServletRequest request, Exception e) {
        
        // 新增:视频流请求特殊处理
        if (isVideoRequest(request)) {
            log.warn("视频流请求发生异常,客户端可能已断开: {}", e.getMessage());
            // 直接返回空响应,避免转换器错误
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .build();
        }

        log.error("其他异常!原因是: ", e);
        BaseResponse error = new BaseResponse(500, e.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }

    // 判断是否为视频流请求
    private boolean isVideoRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        String accept = request.getHeader("Accept");
        return uri != null && (uri.contains("/video") || uri.contains("videoPlay"));
    }
}
2. 优化视频流方法(强烈建议)

在 TaskInfoService.videoPlayRangeOss 或 videoPlay 方法中:

Java

复制代码
public void videoPlayRangeOss(...) {
    try {
        // ... 正常视频流写入逻辑
        response.setContentType("video/mp4");
        // 使用 OutputStream 写入视频数据
    } catch (ClientAbortException | IOException e) {
        // 客户端主动断开连接属于正常现象(用户暂停、拖动进度条、关闭视频等)
        log.info("客户端断开视频连接: {}", e.getMessage());
        // 不要往外抛异常
    } catch (Exception e) {
        log.error("视频播放异常", e);
        throw e; // 其他异常才往上抛
    }
}
3. 在 Controller 层也加保护

Java

复制代码
@GetMapping("/videoPlay")
public void video(HttpServletRequest request, HttpServletResponse response) {
    try {
        taskInfoService.videoPlay(...);
    } catch (ClientAbortException e) {
        log.info("视频流被客户端中断");
    } catch (Exception e) {
        log.error("视频播放失败", e);
        // 这里不要返回 BaseResponse
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
}

额外建议

  1. 抑制 Broken Pipe 日志噪声 (可选):

    YAML

    复制代码
    logging:
      level:
        org.apache.catalina.connector.ClientAbortException: WARN
  2. AOP 注意:你的 CommonWrapAop 也在包裹视频接口,如果里面有异常处理,也需要做类似判断。


请把下面代码贴给我,我帮你精准修改:

  • ControllerAdviceConfig.java 的完整异常处理器代码
  • TaskInfoService.java 中 videoPlayRangeOss 方法的关键部分(尤其是写流的那一段)

这样我可以给你直接可复制的修复代码。

相关推荐
llz_11216 小时前
web-第五次课后作业
前端·后端·http
曾阿伦18 小时前
netcat / ncat / socat 用法详解与示例
linux·http·信息与通信
cyforkk1 天前
破除网络协议迷雾:TCP、TLS 与 HTTP 的“连环套”逻辑
网络协议·tcp/ip·http
VidDown1 天前
视频协议传输全解析:从 HTTP/HTTPS 到 HLS/DASH 的完整旅程
javascript·网络·http·https·编辑器·音视频·视频编解码
Patrick_Wilson2 天前
Cookie 作用域避坑:父域泄漏、同名优先级与多环境隔离
前端·http·浏览器
我喜欢就喜欢2 天前
C++ 连接 Ollama 本地大模型:从原生 HTTP 调用到高性能封装实践
开发语言·c++·http
七夜zippoe2 天前
DolphinDB HTTP API接入:RESTful数据推送
网络协议·http·api·restful·dolphindb
我是一颗柠檬2 天前
【计算机网络全面教学】应用层核心协议,HTTP/DNS/DHCP/FTP/SMTP全解析Day5(2026年)
网络协议·计算机网络·http
mmmayang3 天前
基于 QUIC 的 HTTP_3
网络·网络协议·http