网站建设的项目方案,做网站电话,网站建设是设,北京的互联网公司#x1f337; 古之立大事者#xff0c;不惟有超世之才#xff0c;亦必有坚忍不拔之志 #x1f390; 个人CSND主页——Micro麦可乐的博客 #x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程#xff0c;入门到实战 #x1f33a;《RabbitMQ》… 古之立大事者不惟有超世之才亦必有坚忍不拔之志 个人CSND主页——Micro麦可乐的博客 《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程入门到实战 《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程从基础知识到项目实战 《设计模式》专栏以实际的生活场景为案例进行讲解让大家对设计模式有一个更清晰的理解 《开源项目》本专栏主要介绍目前热门的开源项目带大家快速了解并轻松上手使用 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧均附有完整的代码示例 ✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项并分享一些日常开发的功能小技巧 《Jenkins实战》专栏主要介绍JenkinsDocker的实战教程让你快速掌握项目CI/CD是2024年最新的实战教程 《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧代码样例完整 《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节带你从入门到精通全面掌握这一安全技术 如果文章能够给大家带来一定的帮助欢迎关注、评论互动 视频续播功能实现 - 断点续看从前端到 Spring Boot 后端 1. 前言2. 为什么要做视频续播3. 续播功能原理3.1 常见的续播记录系统架构3.2 常见的触发记录时机 4. 纯前端实现方案4.1 基础实现代码4.2 增强版本地存储 5. 后端Spring Boot实现5.1 数据库表5.2 后端接口5.3 Service服务及Mapper5.4. 前端调用示例 6. 测试与优化7. 结语 1. 前言
在视频网站或在线学习平台中用户观看长视频如课程、电影时常会中途退出。若再次进入时不得不从头开始体验大打折扣。视频续播Resume Playback 功能可以帮助用户保存上次观看位置下次打开时自动跳转到该时间点继续观看大幅提升用户体验。
比如我们常见的B站当你播放中途退出继续访问这个视频的时候会提示 已为您定位至XXXX 的提示如下图 本文博主将从为什么要做续播、续播原理、前端实现、后端实现到测试与优化逐步拆解整个流程并给出完整代码示例帮助小伙伴快速在项目中落地该功能。 2. 为什么要做视频续播
在如今的流媒体时代用户平均每天观看视频时长超过 2.5 小时但其中可能会出现观看会话会被中断临时退出、电话、通知、设备切换等。能否记住播放位置并提供无缝续播体验已成为衡量视频平台专业度的重要指标 提升用户体验 用户无需手动记忆上次进度打开即看 长视频更易于分段观看提高学习/观影效率
增加平台粘性 优质体验能让用户更愿意再次回访延长平台使用时长
数据价值挖掘 记录观看进度可分析用户活跃度、观看习惯用于个性化推荐 3. 续播功能原理
前端监听 视频播放进度将当前时间点currentTime在用户退出或定时时保存。
存储进度
简易方案localStorage针对单设备、单浏览器 复杂方案通过 REST 接口将进度保存到后端数据库支持多设备、多浏览器
恢复进度 页面加载时读取存储的进度将 video 的 currentTime 设置为该值
3.1 常见的续播记录系统架构
如上述所说如果你仅针对单设备、单浏览器可以直接使用本地存储但如果需要多设备支持那么就需要有如下规划
3.2 常见的触发记录时机
前端在出发播放进度记录常见的有以下几种
事件类型记录策略用户行为暂停播放立即记录主动暂停离开页面最后位置记录关闭标签/切换应用播放结束重置位置完整观看进度拖拽延迟记录(防抖动)快速跳转 4. 纯前端实现方案
下面给小伙伴们演示基于原生 HTML5 Video JavaScript 的示例使用 localStorage 做本地保存
4.1 基础实现代码
!DOCTYPE html
html langzh-CN
headmeta charsetUTF-8title视频续播示例/titlestylevideo { width: 100%; max-width: 600px; margin: 20px auto; display: block; }/style
/head
bodyh2视频续播示例/h2!-- 视频播放地址 --video idmyVideo controlssource srchttps://你的视频地址.mp4 typevideo/mp4/video!-- 视频播放监听 --scriptconst video document.getElementById(myVideo);const VIDEO_ID movie-123; //视频标识const STORAGE_KEY video-progress-${VIDEO_ID};// 初始化播放位置const savedTime localStorage.getItem(STORAGE_KEY);if (savedTime) video.currentTime parseFloat(savedTime);// 进度记录函数function saveProgress() {localStorage.setItem(STORAGE_KEY, video.currentTime.toString());}// 事件监听//拖动播放条或进度条播放变化video.addEventListener(timeupdate, throttle(saveProgress, 5000));//暂停video.addEventListener(pause, saveProgress);//播放完成video.addEventListener(ended, () {localStorage.removeItem(STORAGE_KEY);});// 离开或刷新页面时立即保存window.addEventListener(beforeunload, saveProgress);// 节流函数function throttle(func, delay) {let lastCall 0;return function(...args) {const now Date.now();if (now - lastCall delay) {func.apply(this, args);lastCall now;}};}/script
/body
/htmlSTORAGE_KEY 基于视频 URL 唯一标识每个视频分开保存 4.2 增强版本地存储
聪明的小伙伴们上述代码案例就能看出仅仅只能记录并续播最后一次观看的视频那么如果我希望记录5个之前观看中断的视频那么就可以参考以下代码
// 存储完整观看记录
function savePlaybackState() {const state {timestamp: Date.now(),progress: video.currentTime,duration: video.duration,videoId: VIDEO_ID,percentage: (video.currentTime / video.duration * 100).toFixed(1)};// 保存最近5条记录const history JSON.parse(localStorage.getItem(video-history) || []);const newHistory [state,...history.filter(item item.videoId ! VIDEO_ID)].slice(0, 5);localStorage.setItem(video-history, JSON.stringify(newHistory));localStorage.setItem(STORAGE_KEY, video.currentTime.toString());
}5. 后端Spring Boot实现
当需要跨设备同步或用户登录状态下保存进度时可通过后端接口存储。下面示例用 Spring BootMyBatisMySQL 做简易实现供小伙伴们参考
5.1 数据库表
对应的实体模型小伙伴们可以自己生成
CREATE TABLE video_progress (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,video_id VARCHAR(255) NOT NULL,watched_time DOUBLE NOT NULL,update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,UNIQUE KEY idx_user_video (user_id, video_id)
);5.2 后端接口
这里仅仅演示后端记录视频进度的功能相关用户鉴权等小伙伴们自行实现可以参考博主的 《Spring Security》专栏进一步学习
RestController
RequestMapping(/api/video)
public class VideoProgressController {Autowiredprivate VideoProgressService progressService;// 保存或更新进度PostMapping(/progress)public ResponseEntity? saveProgress(RequestBody ProgressRequest req,RequestHeader(X-User-Id) Long userId) {progressService.saveOrUpdate(userId, req.getVideoId(), req.getCurrentTime());return ResponseEntity.ok().build();}// 获取进度GetMapping(/progress)public ResponseEntityDouble getProgress(RequestParam String videoId,RequestHeader(X-User-Id) Long userId) {Double time progressService.getProgress(userId, videoId);return ResponseEntity.ok(time ! null ? time : 0.0);}// 请求 DTOpublic static class ProgressRequest {private String videoId;private Double currentTime;// getters/setters...}
}5.3 Service服务及Mapper
Mapper代码
Mapper
public interface VideoProgressMapper {Select(SELECT * FROM video_progress WHERE user_id#{userId} AND video_id#{videoId})VideoProgress findByUserAndVideo(Param(userId) Long userId, Param(videoId) String videoId);Insert(INSERT INTO video_progress(user_id,video_id,watched_time) VALUES(#{userId},#{videoId},#{watchedTime}) ON DUPLICATE KEY UPDATE watched_time#{watchedTime}, update_timeNOW())void upsert(VideoProgress record);
}Service代码
Service
public class VideoProgressService {Autowiredprivate VideoProgressMapper mapper;public void saveOrUpdate(Long userId, String videoId, Double time) {VideoProgress record new VideoProgress(userId, videoId, time);mapper.upsert(record);}public Double getProgress(Long userId, String videoId) {VideoProgress rec mapper.findByUserAndVideo(userId, videoId);return rec ! null ? rec.getWatchedTime() : null;}
}5.4. 前端调用示例
video idmyVideo controlssource srcmovie.mp4 typevideo/mp4
/videoscript
const API_BASE /api/video;
const video document.getElementById(myVideo);
const VIDEO_ID movie-123;
const userId 42; // 假设已登录并拿到 userId
const STORAGE_KEY video-progress-${VIDEO_ID};// 恢复进度 初始化播放位置
async function loadProgress() {const res await fetch(${API_BASE}/progress?videoId${videoId}, {headers: { X-User-Id: userId }});const time await res.json();if (time 0 time video.duration) {video.currentTime time;}
}// 进度记录函数
function saveProgress() {fetch(${API_BASE}/progress, {method: POST,headers: {Content-Type: application/json,X-User-Id: userId},body: JSON.stringify({ videoId, currentTime: video.currentTime })});}
}// 事件监听
video.addEventListener(timeupdate, throttle(saveProgress, 5000));
video.addEventListener(pause, saveProgress);
video.addEventListener(ended, () {//TODO 后端删除API小伙伴们可自行实现
});// 页面关闭前保存
window.addEventListener(beforeunload, saveProgress);// 节流函数
function throttle(func, delay) {let lastCall 0;return function(...args) {const now Date.now();if (now - lastCall delay) {func.apply(this, args);lastCall now;}};
}
/script6. 测试与优化
测试 模拟网络抖动、断网重连确保进度及时更新 跨设备登录测试在不同设备/浏览器登录同一账号验证进度同步
优化建议 数据校验后端对 currentTime 做合法性校验不超出视频总时长 批量提交可改为用户退出时一次性提交最后进度减少请求次数 缓存 重试前端调用失败时缓存到 IndexedDB下次自动重试 并发合并后端可结合消息队列异步写库减小请求延迟
不同规模平台的实施建议 7. 结语
通过本文示例相信小伙伴已掌握了从本地存储到后端持久化的完整视频续播实现方案。无论是单设备场景下的 localStorage还是支持多端同步的 Spring Boot 数据库方案都能灵活应用到你的项目中。
希望这篇文章能帮助你打造更友好的视频观看体验如果你在实践过程中有任何疑问或更好的扩展思路欢迎在评论区留言最后希望大家 一键三连 给博主一点点鼓励 前端技术专栏回顾
01【前端技术】 ES6 介绍及常用语法说明 02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解 03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案 04 前端开发中深拷贝的循环引用问题从问题复现到完美解决 05 前端AJAX请求上传下载进度监控指南详解与完整代码示例 06 TypeScript 进阶指南 - 使用泛型与keyof约束参数 07 前端实现视频文件动画帧图片提取全攻略 - 附完整代码样例 08 前端函数防抖Debounce完整讲解 - 从原理、应用到完整实现 09 JavaScript异步编程 Async/Await 使用详解从原理到最佳实践 10 前端图片裁剪上传全流程详解从预览到上传的完整流程 11 前端大文件分片上传详解 - Spring Boot 后端接口实现 12 前端实现图片防盗链技术详解 - 原理分析与SpringBoot解决方案