视频断点续播全栈实现:基于HTML5前端与Spring Boot后端
视频断点续播功能实现方案摘要:本文详细介绍了视频断点续播功能的完整实现方案,包含前端与后端的配合实现。前端通过JavaScript记录播放进度并定期保存,后端采用Spring Boot框架存储进度数据并支持视频范围请求(Range Request)。关键技术点包括:前端使用video元素获取播放进度,通过定时器保存状态;后端实现进度存储JPA实体,提供REST API接口;服务器端支持视频文件的字
文章目录
🌐 我的个人网站:乐乐主题创作室
视频断点续播功能实现方案
核心思路
实现视频断点续播需要前后端配合,主要包括:
- 前端记录播放进度
- 后端存储进度信息
- 视频请求支持范围请求(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.简化复杂子图结构
2.关键节点标注:
3.完整流程图
流程说明
用户交互:
用户打开视频页面
播放器自动从后端获取上次播放位置
用户播放、暂停或关闭视频时触发进度保存
前端核心功能:
视频加载时请求上次播放位置
定期(每5秒)向后端发送当前播放进度
播放器暂停时立即保存当前进度
使用Range请求支持断点下载视频
后端处理:
保存用户的视频播放进度到数据库
处理视频流的范围请求(Range Request)
根据用户ID和视频ID检索播放进度
数据存储:
数据库中存储用户ID、视频ID和播放位置
文件系统中存储实际视频文件
此构思图展示了完整的数据流和组件交互,有助于理解整个断点续播功能的实现原理。
🌟 希望这篇指南对你有所帮助!如有问题,欢迎提出 🌟
🌟 如果我的博客对你有帮助、如果你喜欢我的博客内容! 🌟
🌟 请 “👍点赞” “✍️评论” “💙收藏” 一键三连哦!🌟
📅 以上内容技术相关问题😈欢迎一起交流学习👇🏻👇🏻👇🏻🔥
更多推荐
所有评论(0)