🌐 我的个人网站:乐乐主题创作室

在这里插入图片描述

视频断点续播功能实现方案

核心思路

实现视频断点续播需要前后端配合,主要包括:

  1. 前端记录播放进度
  2. 后端存储进度信息
  3. 视频请求支持范围请求(Range Request)

前端实现

HTML结构

<div class="video-container">
  <video id="videoPlayer" controls>
    <source src="/api/video/stream?videoId=123" type="video/mp4">
  </video>
  <div class="video-controls">
    <span id="currentTime">00:00</span> / <span id="totalTime">00:00</span>
  </div>
</div>

JavaScript实现

const videoPlayer = document.getElementById('videoPlayer');
const videoId = '123'; // 视频ID,实际应从URL或其他地方获取

// 页面加载时获取上次播放进度
window.addEventListener('DOMContentLoaded', async () => {
  try {
    const response = await fetch(`/api/video/progress?videoId=${videoId}`);
    const data = await response.json();
    
    if (data.lastPosition) {
      videoPlayer.currentTime = data.lastPosition;
    }
  } catch (error) {
    console.error('获取播放进度失败:', error);
  }
});

// 定期保存播放进度(每5秒)
let progressTimer = null;
videoPlayer.addEventListener('play', () => {
  progressTimer = setInterval(() => {
    saveVideoProgress(videoPlayer.currentTime);
  }, 5000);
});

// 暂停和结束时保存进度
videoPlayer.addEventListener('pause', () => {
  clearInterval(progressTimer);
  saveVideoProgress(videoPlayer.currentTime);
});

videoPlayer.addEventListener('ended', () => {
  clearInterval(progressTimer);
  saveVideoProgress(0); // 播放结束,重置进度
});

// 保存播放进度到后端
async function saveVideoProgress(currentTime) {
  try {
    await fetch('/api/video/progress', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        videoId: videoId,
        position: currentTime
      })
    });
  } catch (error) {
    console.error('保存播放进度失败:', error);
  }
}

Spring Boot后端实现

1.依赖配置(pom.xml)

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
</dependencies>

2.实体类

@Entity
@Data
public class VideoProgress {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String userId;
    private String videoId;
    private Double position;
    private LocalDateTime updatedAt;
    
    @PrePersist
    @PreUpdate
    public void prePersist() {
        updatedAt = LocalDateTime.now();
    }
}

3.存储库接口

@Repository
public interface VideoProgressRepository extends JpaRepository<VideoProgress, Long> {
    Optional<VideoProgress> findByUserIdAndVideoId(String userId, String videoId);
}

4.服务层

@Service
@RequiredArgsConstructor
public class VideoService {
    private final VideoProgressRepository videoProgressRepository;
    
    public VideoProgress getVideoProgress(String userId, String videoId) {
        return videoProgressRepository.findByUserIdAndVideoId(userId, videoId)
                .orElse(new VideoProgress());
    }
    
    public void saveVideoProgress(String userId, String videoId, Double position) {
        VideoProgress progress = videoProgressRepository.findByUserIdAndVideoId(userId, videoId)
                .orElse(new VideoProgress());
        
        progress.setUserId(userId);
        progress.setVideoId(videoId);
        progress.setPosition(position);
        
        videoProgressRepository.save(progress);
    }
    
    public void streamVideo(String videoId, HttpServletRequest request, 
                          HttpServletResponse response) throws IOException {
        File videoFile = new File("/path/to/videos/" + videoId + ".mp4");
        long fileLength = videoFile.length();
        
        String rangeHeader = request.getHeader("Range");
        long startByte = 0;
        long endByte = fileLength - 1;
        
        // 处理范围请求
        if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
            String[] ranges = rangeHeader.substring("bytes=".length()).split("-");
            startByte = Long.parseLong(ranges[0]);
            
            if (ranges.length > 1 && !ranges[1].isEmpty()) {
                endByte = Long.parseLong(ranges[1]);
            }
        }
        
        long contentLength = endByte - startByte + 1;
        
        // 设置响应头
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        response.setContentType("video/mp4");
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Range", "bytes " + startByte + "-" + 
                          endByte + "/" + fileLength);
        response.setContentLengthLong(contentLength);
        
        // 使用try-with-resources确保流关闭
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(videoFile, "r");
             OutputStream outputStream = response.getOutputStream()) {
            
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            randomAccessFile.seek(startByte);
            
            while ((bytesRead = randomAccessFile.read(buffer)) != -1 && contentLength > 0) {
                int bytesToWrite = (int) Math.min(bytesRead, contentLength);
                outputStream.write(buffer, 0, bytesToWrite);
                contentLength -= bytesToWrite;
                
                if (contentLength == 0) {
                    break;
                }
            }
        }
    }
}

5. 控制器

@RestController
@RequestMapping("/api/video")
@RequiredArgsConstructor
public class VideoController {
    private final VideoService videoService;
    
    @GetMapping("/progress")
    public ResponseEntity<Map<String, Object>> getVideoProgress(
            @RequestParam String videoId,
            @AuthenticationPrincipal UserDetails userDetails) {
        
        String userId = userDetails.getUsername();
        VideoProgress progress = videoService.getVideoProgress(userId, videoId);
        
        Map<String, Object> response = new HashMap<>();
        response.put("lastPosition", progress.getPosition());
        
        return ResponseEntity.ok(response);
    }
    
    @PostMapping("/progress")
    public ResponseEntity<Void> saveVideoProgress(
            @RequestBody VideoProgressRequest request,
            @AuthenticationPrincipal UserDetails userDetails) {
        
        String userId = userDetails.getUsername();
        videoService.saveVideoProgress(userId, request.getVideoId(), request.getPosition());
        
        return ResponseEntity.ok().build();
    }
    
    @GetMapping("/stream")
    public void streamVideo(
            @RequestParam String videoId,
            HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        
        videoService.streamVideo(videoId, request, response);
    }
}

@Data
class VideoProgressRequest {
    private String videoId;
    private Double position;
}

实现要点

视频范围请求:使用HTTP的Range头支持断点续传
进度存储:为每个用户存储每个视频的播放进度
定期保存:前端定期发送当前播放位置
恢复播放:加载视频时自动定位到上次观看位置
缓存控制:视频流合理设置缓存控制以提高性能
实现这个功能需要注意用户身份验证、视频访问权限控制和性能优化等方面。

视频断点续播功能构思图

1.简化复杂子图结构

后端
前端
监听事件
请求
VideoService
POST /progress
GET /progress
数据库
保存进度
视频播放器
获取进度

2.关键节点标注:

播放/暂停
加载视频
用户操作
事件类型
保存进度
获取进度
API: POST /progress
API: GET /progress
Spring Controller
VideoProgress表

3.完整流程图

数据存储
Spring Boot后端
API交互
前端
Range请求
部分内容响应
VideoProgress表
视频文件
VideoController
视频流控制器
VideoService
视频流服务
更新进度
获取进度
处理Range请求
数据库
视频文件系统
api/video/progress POST
api/video/progress GET
api/video/stream GET
视频加载请求
视频播放器
用户界面
监听播放事件
监听暂停事件
定期保存进度
立即保存进度
获取上次播放位置

流程说明

用户交互:

用户打开视频页面
播放器自动从后端获取上次播放位置
用户播放、暂停或关闭视频时触发进度保存

前端核心功能:

视频加载时请求上次播放位置
定期(每5秒)向后端发送当前播放进度
播放器暂停时立即保存当前进度
使用Range请求支持断点下载视频

后端处理:

保存用户的视频播放进度到数据库
处理视频流的范围请求(Range Request)
根据用户ID和视频ID检索播放进度

数据存储:

数据库中存储用户ID、视频ID和播放位置
文件系统中存储实际视频文件

此构思图展示了完整的数据流和组件交互,有助于理解整个断点续播功能的实现原理。


🌟 希望这篇指南对你有所帮助!如有问题,欢迎提出 🌟

🌟 如果我的博客对你有帮助、如果你喜欢我的博客内容! 🌟

🌟 请 “👍点赞” “✍️评论” “💙收藏” 一键三连哦!🌟

📅 以上内容技术相关问题😈欢迎一起交流学习👇🏻👇🏻👇🏻🔥

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐