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

Spring Boot+Angular视频点播:字节数组转Blob播放失败求助

问题根源及解决方案

核心问题分析

  1. 服务端未支持HTTP范围请求(Range):HTML5 video播放视频依赖分段加载,浏览器会发送Range请求头获取视频的不同片段,你的服务端当前仅返回固定1MB的开头数据,且未设置必要的响应头(如Accept-Ranges、Content-Range),导致浏览器无法识别视频格式。
  2. 服务端响应格式错误:直接返回byte[]时,Spring Boot默认会将其序列化为JSON或文本,而非二进制流,客户端拿到的是损坏的二进制数据。
  3. 客户端数据处理错误:
    • 未设置请求的responseType为arraybuffer,导致响应被解析为字符串,后续转Uint8Array时数据失真。
    • Blob构造函数参数错误,需传入包含TypedArray的数组([uint8array])而非直接传入TypedArray。

服务端修复代码(Spring Boot)

改为支持Range请求的流式返回,同时设置正确的响应头:

import org.springframework.core.io.FileSystemResource;
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 javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
public class VideoStreamController {

    @GetMapping("/stream")
    public ResponseEntity<FileSystemResource> streamVideo(
            @RequestParam String filepath,
            @RequestHeader(value = "Range", required = false) String rangeHeader) throws IOException {

        File videoFile = new File(filepath);
        Path videoPath = Paths.get(filepath);
        long fileLength = videoFile.length();

        if (rangeHeader == null) {
            // 返回完整视频(首次请求或不支持Range时)
            HttpHeaders headers = new HttpHeaders();
            headers.setContentLength(fileLength);
            headers.setContentType(MediaType.valueOf("video/mp4"));
            headers.set(HttpHeaders.ACCEPT_RANGES, "bytes");
            return new ResponseEntity<>(new FileSystemResource(videoFile), headers, HttpStatus.OK);
        }

        // 处理Range请求
        String[] ranges = rangeHeader.replace("bytes=", "").split("-");
        long start = Long.parseLong(ranges[0]);
        long end = ranges.length > 1 ? Long.parseLong(ranges[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;

        HttpHeaders headers = new HttpHeaders();
        headers.setContentLength(contentLength);
        headers.setContentType(MediaType.valueOf("video/mp4"));
        headers.set(HttpHeaders.ACCEPT_RANGES, "bytes");
        headers.set(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + fileLength);

        // 流式返回指定范围的内容,避免加载整个文件到内存
        return new ResponseEntity<>(new FileSystemResource(videoFile) {
            @Override
            public long contentLength() throws IOException {
                return contentLength;
            }

            @Override
            public java.io.InputStream getInputStream() throws IOException {
                return Files.newInputStream(videoPath).skip(start);
            }
        }, headers, HttpStatus.PARTIAL_CONTENT);
    }
}

客户端修复代码(JavaScript/Angular)

调整请求配置和Blob构造逻辑:

原生JavaScript(Fetch)

const player = document.getElementById("video-player");

fetch(`/stream?filepath=your-video-file.mp4`)
.then(response => {
  if (response.ok || response.status === 206) { // 206为分段响应状态码
    return response.arrayBuffer();
  }
  throw new Error('视频请求失败');
})
.then(buffer => {
  const uint8array = new Uint8Array(buffer);
  // Blob构造函数需传入包含TypedArray的数组
  const blob = new Blob([uint8array], { type: 'video/mp4' });
  player.src = URL.createObjectURL(blob);
  return player.play();
})
.catch(error => console.error('播放错误:', error));

Angular(HttpClient)

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-video-player',
  template: `<video id="video-player" controls></video>`
})
export class VideoPlayerComponent {
  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.loadVideo('your-video-file.mp4');
  }

  loadVideo(filepath: string) {
    this.http.get(`/stream?filepath=${filepath}`, {
      responseType: 'arraybuffer'
    }).subscribe(buffer => {
      const uint8array = new Uint8Array(buffer);
      const blob = new Blob([uint8array], { type: 'video/mp4' });
      const player = document.getElementById("video-player") as HTMLVideoElement;
      player.src = URL.createObjectURL(blob);
      player.play().catch(err => console.error(err));
    });
  }
}

额外注意事项

  • 确保视频文件为标准MP4格式(H.264编码),浏览器对视频编码格式有严格兼容性要求。
  • 服务端无需手动拆分固定大小的分片,浏览器的video标签会自动根据播放进度发送对应的Range请求。
  • 生产环境中需添加文件路径的权限校验,避免任意文件读取风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:35:17