视频续播功能实现 - 断点续看从前端到 Spring Boot 后端

视频续播功能实现 - 断点续看从前端到 Spring Boot 后端
在这里插入图片描述
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

视频续播功能实现 - 断点续看从前端到 Spring Boot 后端

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 基础实现代码

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>视频续播示例</title><style>video{width: 100%;max-width: 600px;margin: 20px auto;display: block;}</style></head><body><h2>视频续播示例</h2><!-- 视频播放地址 --><videoid="myVideo"controls><sourcesrc="https://你的视频地址.mp4"type="video/mp4"></video><!-- 视频播放监听 --><script>const video = document.getElementById('myVideo');constVIDEO_ID='movie-123';//视频标识constSTORAGE_KEY=`video-progress-${VIDEO_ID}`;// 初始化播放位置const savedTime = localStorage.getItem(STORAGE_KEY);if(savedTime) video.currentTime =parseFloat(savedTime);// 进度记录函数functionsaveProgress(){ 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);// 节流函数functionthrottle(func, delay){let lastCall =0;returnfunction(...args){const now = Date.now();if(now - lastCall >= delay){func.apply(this, args); lastCall = now;}};}</script></body></html>
STORAGE_KEY 基于视频 URL 唯一标识,每个视频分开保存

4.2 增强版本地存储

聪明的小伙伴们上述代码案例就能看出,仅仅只能记录并续播最后一次观看的视频,那么如果我希望记录5个之前观看中断的视频,那么就可以参考以下代码:

// 存储完整观看记录functionsavePlaybackState(){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 数据库表

对应的实体模型小伙伴们可以自己生成

CREATETABLE video_progress ( id BIGINTAUTO_INCREMENTPRIMARYKEY, user_id BIGINTNOTNULL, video_id VARCHAR(255)NOTNULL, watched_time DOUBLENOTNULL, update_time TIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,UNIQUEKEY idx_user_video (user_id, video_id));

5.2 后端接口

这里仅仅演示后端记录视频进度的功能,相关用户鉴权等小伙伴们自行实现,可以参考博主的 《Spring Security》专栏进一步学习

@RestController@RequestMapping("/api/video")publicclassVideoProgressController{@AutowiredprivateVideoProgressService progressService;// 保存或更新进度@PostMapping("/progress")publicResponseEntity<?>saveProgress(@RequestBodyProgressRequest req,@RequestHeader("X-User-Id")Long userId){ progressService.saveOrUpdate(userId, req.getVideoId(), req.getCurrentTime());returnResponseEntity.ok().build();}// 获取进度@GetMapping("/progress")publicResponseEntity<Double>getProgress(@RequestParamString videoId,@RequestHeader("X-User-Id")Long userId){Double time = progressService.getProgress(userId, videoId);returnResponseEntity.ok(time !=null? time :0.0);}// 请求 DTOpublicstaticclassProgressRequest{privateString videoId;privateDouble currentTime;// getters/setters...}}

5.3 Service服务及Mapper

Mapper代码

@MapperpublicinterfaceVideoProgressMapper{@Select("SELECT * FROM video_progress WHERE user_id=#{userId} AND video_id=#{videoId}")VideoProgressfindByUserAndVideo(@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_time=NOW()")voidupsert(VideoProgress record);}

Service代码

@ServicepublicclassVideoProgressService{@AutowiredprivateVideoProgressMapper mapper;publicvoidsaveOrUpdate(Long userId,String videoId,Double time){VideoProgress record =newVideoProgress(userId, videoId, time); mapper.upsert(record);}publicDoublegetProgress(Long userId,String videoId){VideoProgress rec = mapper.findByUserAndVideo(userId, videoId);return rec !=null? rec.getWatchedTime():null;}}

5.4. 前端调用示例

<videoid="myVideo"controls><sourcesrc="movie.mp4"type="video/mp4"></video><script>constAPI_BASE='/api/video';const video = document.getElementById('myVideo');constVIDEO_ID='movie-123';const userId =42;// 假设已登录并拿到 userIdconstSTORAGE_KEY=`video-progress-${VIDEO_ID}`;// 恢复进度 初始化播放位置asyncfunctionloadProgress(){const res =awaitfetch(`${API_BASE}/progress?videoId=${videoId}`,{headers:{'X-User-Id': userId }});const time =await res.json();if(time >0&& time < video.duration){ video.currentTime = time;}}// 进度记录函数functionsaveProgress(){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);// 节流函数functionthrottle(func, delay){let lastCall =0;returnfunction(...args){const now = Date.now();if(now - lastCall >= delay){func.apply(this, args); lastCall = now;}};}</script>

6. 测试与优化

测试
模拟网络抖动、断网重连,确保进度及时更新
跨设备登录测试:在不同设备/浏览器登录同一账号,验证进度同步

优化建议
数据校验:后端对 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解决方案

Read more

AI写论文提示词指令大全,快速写论文

AI写论文提示词指令大全,快速写论文

目录 * 一、十大学术写作提示词 * 1、研究主题 * 2、研究问题 * 3、论文架构 * 4、学术论证 * 5、文献关键要素 * 6、专业文本可读性转换 * 7、学术语言规范化 * 8、提高语言准确性 * 9、多维度、深层论证 * 10、优化文本结构 * 二、快速写论文提示词 * 1、确认研究选题 * 2、整理相关资料 * 3、快速完成论文大纲 * 4、整合文献引用 * 5、论文校对 最近很多小伙伴问我关于DeepSeek搞学术的问题,我自己也实操测试了一段时间,对于刚入坑AI写作的朋友们,DeepSeek无疑是一个理想的起点。它的中文理解能力出色,操作界面简洁明了,就像是给你配了个"AI写作入门教练"。许多新手表示:“第一次用DeepSeek就上手,不需要学习复杂的提示词技巧,输入需求就能得到满意回复。

2026年主流AI生成PPT工具横评:6款实测,谁是你的效率搭档?

2026年主流AI生成PPT工具横评:6款实测,谁是你的效率搭档?

原创声明:本文为作者原创,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 摘要:AI生成PPT工具正成为职场办公的重要辅助手段。本文基于2026年3月的实测体验,对市面上6款主流AI PPT工具进行客观评测,从核心功能、技术特点、适用场景、费用机制等维度展开分析,涵盖7牛AI PPT、百度文库PPT、Kimi PPT、Gamma、讯飞智文、WPS AI等产品。不吹不黑,只讲实话,帮助读者根据自身需求找到合适的工具。 关键词:AI生成PPT;AI做PPT;工具横评;办公效率;智能办公 一、引言:AI做PPT,如今发展到哪一步了? 凌晨三点还在调整PPT字体间距?因为领导一句“逻辑不对”就要推翻重排版式?这种被PPT支配的日子,很多职场人都经历过。 数据显示,普通职场人年均花费87小时在幻灯片制作上,其中42%的时间耗在排版、

人工智能:大语言模型(LLM)原理与应用实战

人工智能:大语言模型(LLM)原理与应用实战

人工智能:大语言模型(LLM)原理与应用实战 1.1 本章学习目标与重点 💡 学习目标:掌握大语言模型的核心原理、训练流程与微调方法,学会基于开源大语言模型完成定制化对话与文本生成任务。 💡 学习重点:理解大语言模型的Transformer decoder-only架构,掌握指令微调与RLHF技术,能够使用LoRA高效微调开源LLM。 1.2 大语言模型的核心概念与发展历程 1.2.1 什么是大语言模型 💡 大语言模型(Large Language Model, LLM)是参数量达到十亿级甚至万亿级的Transformer-based模型。它通过在海量文本数据上进行预训练,学习语言的语法、语义、常识和推理能力。 LLM的核心能力包括文本生成、理解、翻译、摘要、问答等。它可以处理复杂的自然语言任务,无需针对每个任务单独设计模型结构。 LLM与传统NLP模型的核心区别: * 参数量级:传统模型参数量通常在千万级,LLM参数量可达十亿到万亿级。 * 训练数据:传统模型依赖标注数据,LLM使用海量无标注文本进行预训练。 * 能力边界:传统模型只能处理单一任务,LL

『告别手工测试:AI 自动化测试覆盖 90% 场景的秘诀』

『告别手工测试:AI 自动化测试覆盖 90% 场景的秘诀』

在 AI 技术飞速渗透各行各业的当下,我们早已告别 “谈 AI 色变” 的观望阶段,迈入 “用 AI 提效” 的实战时代 💡。无论是代码编写时的智能辅助 💻、数据处理中的自动化流程 📊,还是行业场景里的精准解决方案 ,AI 正以润物细无声的方式,重构着我们的工作逻辑与行业生态 🌱。今天,我想结合自身实战经验,带你深入探索 AI 技术如何打破传统工作壁垒 🧱,让 AI 真正从 “概念” 变为 “实用工具” ,为你的工作与行业发展注入新动能 ✨。 文章目录 * 告别手工测试:AI 自动化测试覆盖 90% 场景的秘诀 🤖🧪 * 一、引言:从手工到AI,测试革命的浪潮 🌊🌊 * 1. 传统手工测试的困境 ⚠️ * 2. 自动化测试的初步尝试 🤖 * 3. AI驱动自动化测试的崛起 🌟🤖 * 二、AI自动化测试的关键技术栈 🧠⚙️ * 1.