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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 04:55:19