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

Spring Boot实现HTML5视频流控制器端点的正确方式咨询

实现Spring Boot HTML5视频源控制器端点的正确方式

嘿,我来帮你搞定这个Spring Boot媒体服务器的视频端点问题!你的基础思路是对的,但还有几个关键细节需要完善,才能让HTML5视频播放器正常工作(比如支持拖拽播放、异常处理这些)。

一、基础完善版实现

首先补全你未写完的代码,同时加上必要的校验和异常处理:

import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;

@RestController
public class VideoController {

    // 假设你的视频文件存储在这个目录下,实际可以配置到application.properties里
    private static final String VIDEO_STORAGE_PATH = "/path/to/your/videos/";

    @GetMapping(value = "/videosrc", produces = "video/mp4")
    public ResponseEntity<FileSystemResource> videoSource(@RequestParam(value = "id", required = true) int id) {
        // 根据id拼接视频文件路径,这里假设文件名是id.mp4,你可以根据自己的规则调整
        File videoFile = new File(VIDEO_STORAGE_PATH + id + ".mp4");
        
        // 校验文件是否存在且可读
        if (!videoFile.exists() || !videoFile.canRead()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        return ResponseEntity.ok(new FileSystemResource(videoFile));
    }
}

二、关键优化:支持范围请求(Range Requests)

HTML5视频播放器默认会发送Range请求头来分段加载视频(比如只请求视频的第0-1024字节),这样用户可以拖拽进度条。如果你的端点不支持Range请求,视频可能只能从头播放,没法拖拽。

Spring Boot已经内置了ResourceHttpMessageConverter来处理这种场景,但如果需要手动自定义逻辑,可以这样实现:

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.file.Files;

@RestController
public class VideoController {

    private static final String VIDEO_STORAGE_PATH = "/path/to/your/videos/";

    @GetMapping(value = "/videosrc", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<byte[]> videoSource(
            @RequestParam(value = "id", required = true) int id,
            @RequestHeader(value = "Range", required = false) String rangeHeader) throws Exception {
        
        File videoFile = new File(VIDEO_STORAGE_PATH + id + ".mp4");
        if (!videoFile.exists() || !videoFile.canRead()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        long fileLength = videoFile.length();
        if (rangeHeader == null) {
            // 没有Range请求,返回完整视频
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength));
            headers.add(HttpHeaders.CONTENT_TYPE, "video/mp4");
            return new ResponseEntity<>(Files.readAllBytes(videoFile.toPath()), headers, HttpStatus.OK);
        }

        // 处理Range请求
        String[] rangeParts = rangeHeader.replace("bytes=", "").split("-");
        long start = Long.parseLong(rangeParts[0]);
        long end = rangeParts.length > 1 ? Long.parseLong(rangeParts[1]) : fileLength - 1;
        if (start >= fileLength) {
            return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
        }

        end = Math.min(end, fileLength - 1);
        long contentLength = end - start + 1;
        byte[] content = new byte[(int) contentLength];
        
        try (RandomAccessFile raf = new RandomAccessFile(videoFile, "r")) {
            raf.seek(start);
            raf.read(content);
        }

        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + fileLength);
        headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
        headers.add(HttpHeaders.CONTENT_TYPE, "video/mp4");
        return new ResponseEntity<>(content, headers, HttpStatus.PARTIAL_CONTENT);
    }
}

三、额外性能优化:流式输出大视频

如果你的视频文件很大,直接返回byte[]会占用大量内存,推荐使用StreamingResponseBody来流式输出,避免内存溢出:

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.File;
import java.nio.file.Files;

@RestController
public class VideoController {

    private static final String VIDEO_STORAGE_PATH = "/path/to/your/videos/";

    @GetMapping(value = "/videosrc", produces = "video/mp4")
    public ResponseEntity<StreamingResponseBody> streamVideo(@RequestParam int id) throws Exception {
        File videoFile = new File(VIDEO_STORAGE_PATH + id + ".mp4");
        if (!videoFile.exists()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        StreamingResponseBody responseBody = outputStream -> {
            Files.copy(videoFile.toPath(), outputStream);
        };

        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType("video/mp4"))
                .contentLength(videoFile.length())
                .body(responseBody);
    }
}

这样实现后,HTML5的<video>标签就可以直接通过src="/videosrc?id=1"来加载视频了,而且支持拖拽、分段加载这些核心功能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:32:58