Spring Boot+Angular视频点播:字节数组转Blob播放失败求助
问题根源及解决方案
核心问题分析
- 服务端未支持HTTP范围请求(Range):HTML5 video播放视频依赖分段加载,浏览器会发送
Range请求头获取视频的不同片段,你的服务端当前仅返回固定1MB的开头数据,且未设置必要的响应头(如Accept-Ranges、Content-Range),导致浏览器无法识别视频格式。 - 服务端响应格式错误:直接返回
byte[]时,Spring Boot默认会将其序列化为JSON或文本,而非二进制流,客户端拿到的是损坏的二进制数据。 - 客户端数据处理错误:
- 未设置请求的
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
相关产品推荐
相关产品推荐

