基于Spring Boot(Webflux)的视频流API:如何实现断点续播?
视频播放进度保存与续播提示实现方案
核心逻辑
要实现「离开保存进度、返回提示续播」,需要前端负责进度捕获与上报,后端负责存储与查询,关键是避免仅依赖页面关闭时机上报——因为浏览器关闭时异步请求可能被中断,导致进度丢失。
前端实现要点
- 定时主动上报:每30秒(或根据业务调整间隔),在视频播放过程中调用后端接口,上报当前播放时间戳、用户ID、视频ID。用户暂停视频时也可立即上报,保证进度尽可能新。
- 页面离开/关闭时兜底上报:监听
visibilitychange(切换标签页)或beforeunload(关闭标签页)事件,用navigator.sendBeacon()发送进度数据。这个API是浏览器专门为页面卸载时的异步请求设计的,会保证请求完成后再关闭页面,比普通AJAX可靠。document.addEventListener('visibilitychange', () => { if (document.hidden) { const progress = video.currentTime; navigator.sendBeacon('/api/progress/save', JSON.stringify({ userId: 'xxx', videoId: 'xxx', progress: progress })); } }); - 页面加载时获取进度:打开视频页面时,先请求后端接口查询该用户该视频的最近进度,如果进度大于0且未达到视频总时长,弹出「是否从xx秒继续播放」的提示。
后端Webflux实现要点
- 定义进度实体:
@Data @Table("play_progress") public class PlayProgress { @Id private String id; private String userId; private String videoId; private Long progress; // 播放进度,单位:秒 private LocalDateTime updateTime; } - 响应式Repository:用R2DBC或MongoDB的响应式仓库,实现upsert(存在则更新,不存在则插入)逻辑:
public interface PlayProgressRepository extends ReactiveCrudRepository<PlayProgress, String> { @Query("INSERT INTO play_progress (user_id, video_id, progress, update_time) " + "VALUES (:userId, :videoId, :progress, :updateTime) " + "ON DUPLICATE KEY UPDATE progress = :progress, update_time = :updateTime") Mono<Void> upsert(String userId, String videoId, Long progress, LocalDateTime updateTime); Mono<PlayProgress> findByUserIdAndVideoId(String userId, String videoId); } - 编写API接口:
@RestController @RequestMapping("/api/progress") public class PlayProgressController { private final PlayProgressRepository repository; public PlayProgressController(PlayProgressRepository repository) { this.repository = repository; } @PostMapping("/save") public Mono<Void> saveProgress(@RequestBody PlayProgressRequest request) { return repository.upsert( request.getUserId(), request.getVideoId(), request.getProgress(), LocalDateTime.now() ); } @GetMapping("/query") public Mono<PlayProgress> queryProgress(@RequestParam String userId, @RequestParam String videoId) { return repository.findByUserIdAndVideoId(userId); } }
针对你的问题解答
不是必须仅在关闭标签页时将时间戳存入数据库,反而不建议只依赖这个时机——浏览器关闭时的普通AJAX请求大概率会被中断,导致进度丢失。更可靠的方案是「定时主动上报 + 页面离开时sendBeacon兜底上报」的组合,既保证进度实时更新,又最大化避免意外关闭时的进度丢失。
额外优化建议
- 前端可先用localStorage缓存进度,页面加载时先读取本地缓存快速显示提示,再请求后端接口同步最新数据,提升用户体验。
- 后端可添加逻辑:如果两次上报的进度差小于5秒,直接忽略更新,减少数据库写入压力。
- 针对未登录用户,可用浏览器指纹或localStorage存储进度,用户登录后再同步到后端数据库。
内容的提问来源于stack exchange,提问作者Tokoro-San
相关产品推荐
相关产品推荐

