如何使用Spring Boot(WebFlux)和Angular实现视频流并解决接口响应异常问题
问题定位
后端问题
@RequestHeader("Range")默认必填,浏览器首次请求视频资源不会携带Range头,会直接返回400错误- 仅接收Range参数但未做分片逻辑处理,直接返回完整Resource,无法正确响应206分片请求,视频拖动进度条等操作会失效
- 若项目打包为jar包,classpath下的资源无法通过普通Resource读取流的方式正常获取(可选注意点)
前端问题
- Angular HttpClient默认响应类型为
json,你未指定responseType: 'blob',会将返回的二进制视频流当做JSON解析,直接触发解析错误 - 泛型指定为
[]和实际返回的二进制类型完全不匹配 - 拿到的视频流未经过Angular安全策略处理,直接赋值给
[src]会被安全拦截器拦截,无法正常加载
解决办法
后端代码修正
import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourceRegion; // 类内其他代码 private static String FORMAT = "classpath:videos/%s.mp4"; @Autowired private ResourceLoader resourceLoader; @GetMapping(value = "video/{title}", produces = "video/mp4") public Mono<ResourceRegion> getVideos(@PathVariable String title, @RequestHeader(value = "Range", required = false) String range) throws IOException { Resource resource = resourceLoader.getResource(String.format(FORMAT, title)); // 分片大小可自定义,这里设为1MB long chunkSize = 1024 * 1024; long contentLength = resource.contentLength(); if (range == null) { // 无Range头返回第一块 return Mono.just(new ResourceRegion(resource, 0, Math.min(chunkSize, contentLength))); } // 解析Range头,格式为 bytes=start-end String[] ranges = range.replace("bytes=", "").split("-"); long start = Long.parseLong(ranges[0]); long end = ranges.length > 1 && !ranges[1].isEmpty() ? Long.parseLong(ranges[1]) : Math.min(start + chunkSize, contentLength - 1); long regionLength = end - start + 1; return Mono.just(new ResourceRegion(resource, start, regionLength)); }
前端代码修正
1. 服务层代码修改
getVideo(title: string): Observable<Blob> { return this.http .get(BASIC_URL + `api/video/${title}`, { headers: this.createRange(), responseType: 'blob' // 关键:指定响应类型为二进制blob }) .pipe( tap((_) => this.log('Getting video chunks')), catchError(this.handleError<Blob>('Error getting video', new Blob())) ); }
2. 组件层代码修改
需引入DomSanitizer处理安全URL:
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; // ...组件内代码 videoInByte: SafeUrl; constructor(private sanitizer: DomSanitizer, private videoService: VideoService) {} loadVideo(title: string) { this.videoService.getVideo(title).subscribe(blob => { const blobUrl = URL.createObjectURL(blob); // 绕过安全策略校验 this.videoInByte = this.sanitizer.bypassSecurityTrustUrl(blobUrl); }); }
3. HTML代码无需修改即可正常使用
<div class="container"> <h4>Spring Boot Video streaming</h4> <video [src]='videoInByte' width="700px" height="400px" controls preload="none"> </video> </div>
可选优化:如果接口无鉴权,可直接给video的src赋值为接口地址,无需手动发起请求,浏览器会自动处理Range分片逻辑:<video [src]="BASIC_URL + 'api/video/xxx'" ...>
内容的提问来源于stack exchange,提问作者Syed Saad
相关产品推荐
相关产品推荐

