在这里插入图片描述

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目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服务及Mapper
      • 5.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 lang="zh-CN">
<head><meta charset="UTF-8"><title>视频续播示例</title><style>video { width: 100%; max-width: 600px; margin: 20px auto; display: block; }</style>
</head>
<body><h2>视频续播示例</h2><!-- 视频播放地址 --><video id="myVideo" controls><source src="https://你的视频地址.mp4" type="video/mp4"></video><!-- 视频播放监听 --><script>const 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>
</html>

STORAGE_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 ResponseEntity<Double> 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_time=NOW()")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 id="myVideo" controls><source src="movie.mp4" type="video/mp4">
</video><script>
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;}};
}
</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解决方案
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/86176.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86176.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/86176.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【工具】Linux 中 find 命令使用教程

find 命令是 Linux 系统中最强大、最灵活的文件搜索工具&#xff0c;其能力远超简单的文件名匹配。掌握 find 能让你在复杂的文件系统中精准定位目标&#xff0c;实现高效的文件管理。 一、命令结构与核心概念 find [起始路径] [选项] [表达式]起始路径&#xff1a;搜索的根目…

0629-

0629 0629操作3. 权限 0629 操作 进入数据库 mysql -uroot -proot123 .use idatabase; select * from customer; 2.select distinct name&#xff0c;idnum from customer; 3.UPDATE customer SET idnum left(MD5(idnum),16); 4. UPDATE customer SET phone CONCAT( LEFT(p…

JVM调优实战 Day 6:JVM性能监控工具实战

【JVM调优实战 Day 6】JVM性能监控工具实战 文章简述 在Java应用的性能优化过程中&#xff0c;JVM性能监控工具是不可或缺的“眼睛”。它们能够帮助开发者实时掌握系统运行状态&#xff0c;识别性能瓶颈&#xff0c;并为后续调优提供数据支撑。本文作为“JVM调优实战”系列的第…

【嘉立创EDA】PCB 如何按板框轮廓进行铺铜

文章路标👉 :one: 文章解决问题:two: 主题内容:three: 参考方法be end..1️⃣ 文章解决问题 操作环境:嘉立创EDA专业版 V2.2.40 本文使用嘉立创EDA,描述如何在PCB设计时,直接使用板框轮廓进行铺铜。本文将此过程记录,以供有需要的读者参考。 2️⃣ 主题内容 在PCB设计…

dockerfile命令及构建

一&#xff0c;dockerfile常用命令 命令介绍FROM–指定基础镜像LABEL作者信息USER切换运行属主身份WORKDUR切换工作目录ENV用于docker容器设置环境变量RUN用来执行命令行的命令COPY把宿主机文件复制到镜像中去ADD将文件路径复制添加到容器内部路径EXPOSE为容器打开指定要监听的…

uniApp实战四:网络请求封装

文章目录 1.最终效果预览2.请求封装3.创建config配置文件4.创建api请求5.页面调用 说明&#xff1a;当前笔记基于Vue3开发&#xff0c;HbuilderX版本4.66 1.最终效果预览 2.请求封装 在util/request.js下创建js文件&#xff0c;代码如下 import config from /configconst tim…

MCP协议全解:大模型时代的能力开放与服务集成最佳实践

一、MCP协议是什么&#xff1f; MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是大模型和多智能体&#xff08;Agent&#xff09;生态中&#xff0c;用于标准化描述和传递上下文信息、能力开放、服务集成的协议。它的目标是让不同模型、Agent…

oracle 返回最新记录

在Oracle数据库中&#xff0c;如果你想获取一个表中基于某些条件的最新记录&#xff0c;通常有两种常见的方法&#xff1a;使用ROWID或者使用带有ORDER BY和ROWNUM的子查询。下面我将介绍这两种方法的基本用法。 方法1&#xff1a;使用ROWID 如果你的表有一个时间戳字段或者递…

华为云服务器:Can’t connect to MySql server on ‘localhost’(10060)

本地远程连接服务器数据库&#xff0c;提示10060 在 Ubuntu/Debian 系统中&#xff0c;检查 3306 端口是否开启需要从两个方面验证&#xff1a;MySQL 服务是否监听该端口以及防火墙是否允许外部访问该端口。以下是具体步骤&#xff1a; sudo ufw status查看到为开启mysql端口 …

利用Percona XtraDB Cluster搭建MySql高可用集群

引言拉取镜像创建单节点实例(一般在测试环境中使用)自定义网络(集群间相互隔离)映射数据目录创建docker-compose PXC容器用docker-compose启动PXC集群集群验证数据库负载均衡的必要性Haproxy负载均衡器部署故障排查引言 告别单点故障,拥抱持续可用——构建基于 Percona X…

Leetcode 3592. Inverse Coin Change

Leetcode 3592. Inverse Coin Change 1. 解题思路2. 代码实现 题目链接&#xff1a;3592. Inverse Coin Change 1. 解题思路 这一题的话思路上我们走的是一个贪婪算法的思路&#xff0c;即从小到大依次考察&#xff0c;显然&#xff0c;每一次当前最小的非零面额有且必有当前…

打造属于你的AI智能体,从数据开始 —— 使用 Bright Data MCP+Trae快速构建垂直智能体

一、AI智能体的机遇与挑战 最近这两年全民AI热潮开始&#xff0c;各种智能体应用层出不穷。在AI智能体火热的当下&#xff0c;越来越多开发者想要构建自己的智能体&#xff0c;特别是垂直领域&#xff0c;需求更是旺盛。比如招聘助手、电商导购、财经分析师等等。从技术角度来…

嵌入式自学四十八天

时钟 cpu528MHz&#xff0c; PLL&#xff1a;锁相环电路 倍频功能&#xff1a;Fin*n Fout Prescale&#xff1a; 预分频器 降频 Fin/m Fout PFD&#xff1a;相位分子分频器 Fin *n/m Fout 时钟开了后&#xff0c;先到时钟根产生器&#xff0c;对时钟频率更改&…

光谱相机应对复杂环境条件的关键技术与方案

一、极端温度适应性‌ ‌主动温控系统‌ ‌半导体冷却&#xff08;TEC&#xff09;‌&#xff1a;维持探测器在5-40℃工作区间&#xff0c;防止高温噪声&#xff08;如SPECIM FX17&#xff09;。 ‌散热结构‌&#xff1a;铝合金外壳散热鳍片&#xff0c;工业级相机可在-10℃…

个人技术文档库构建实践:基于Cursor和GitHub的知识管理系统(含cursor rules)

技术选型 核心工具链 Cursor编辑器&#xff1a;AI辅助写作&#xff0c;智能补全和结构优化GitHub&#xff1a;版本控制、跨设备同步、团队协作Markdown&#xff1a;轻量级格式&#xff0c;跨平台兼容&#xff0c;与Git完美集成 与主流工具对比 选择CursorGitHub适合&#xf…

烟花爆竹生产企业库房存储安全风险预警系统

烟花爆竹生产企业库房存储安全风险预警系统是保障库房物资安全、规范作业流程、防范安全事故的重要技术手段&#xff0c;涵盖多个关键预警功能。​ 温湿度预警​ 在库房内安装温湿度传感器&#xff0c;这些传感器如同敏锐的“环境感知员”&#xff0c;能够实时监测库房内环境变…

LINUX 625 DNS域名管理系统

建安错题 根据《安全色》&#xff0c;红、黄、蓝、绿四种安全色各自传递着不同的安全含义和信息,其中表示要求人们必须遵守的规定的颜色是()。 根据《安全色》国家标准&#xff08;GB 2893-2008&#xff09;&#xff0c;四种安全色的含义如下&#xff1a; ​​红色​​&#…

FastMCP框架进行MCP开发:(三)从SSE升级到SteamableHTTP

一、前言 在MCP&#xff08;Model Context Protocol&#xff09;中&#xff0c;Streamable HTTP和SSE&#xff08;Server-Sent Events&#xff09;都是用于实现客户端与服务器之间通信的传输机制。然而&#xff0c;它们在设计、功能以及性能表现上有着显著的区别。 二、SSE在…

Android 15 变更及适配攻略

2025年的第一篇Android适配&#xff0c;比以往来的更晚一些。废话不多说&#xff0c;我们开始&#xff01;&#xff01; 准备工作 首先将我们项目中的 targetSdk和compileSdk 升至 35。 推荐使用Android Studio Koala Feature Drop | 2024.1.2或更高版本。AGP版本最低升级到…

Vue项目使用defer优化页面白屏,性能优化提升,秒加载!!!

defer表示延迟加载&#xff0c;针对大量节点的渲染加载&#xff0c;结合使用关键帧requestAnimationFrame的形式来分片加载&#xff0c;可以优化白屏时间 知识补充&#xff1a; requestAnimationFrame requestAnimationFrame 是根据帧数来执行回调函数的&#xff0c;就是屏幕…