基于timeupdate与localStorage的视频断点续播:从基础实现到性能优化
1. 视频断点续播的核心原理每次看视频看到一半被迫退出再打开又要从头开始这种体验实在太糟糕了。作为开发者我们可以用HTML5 Video API的timeupdate事件配合localStorage轻松解决这个问题。想象一下这就像看书时夹了个书签下次打开直接翻到上次看到的位置。核心原理其实很简单当视频播放时持续监听播放进度变化timeupdate事件把当前时间点currentTime存到浏览器本地localStorage。下次打开页面时先从本地读取保存的时间点让视频直接跳转到这个位置继续播放。整个过程就像这样// 获取视频元素 const video document.querySelector(video); // 监听播放进度变化 video.addEventListener(timeupdate, () { // 把当前播放时间存入本地存储 localStorage.setItem(videoProgress, video.currentTime); }); // 页面加载时检查是否有保存的进度 window.addEventListener(load, () { const savedTime localStorage.getItem(videoProgress); if (savedTime) { video.currentTime parseFloat(savedTime); } });2. 基础实现方案2.1 最小可用版本先来看个最基础的实现我用这个方案帮朋友快速解决了他的在线课程网站需求video idmyVideo controls srclecture.mp4/video script const video document.getElementById(myVideo); const videoId lecture_101; // 每个视频唯一标识 // 恢复播放进度 const savedTime localStorage.getItem(videoTime_${videoId}); if (savedTime) { video.currentTime parseFloat(savedTime); } // 实时保存进度简单版 video.addEventListener(timeupdate, () { localStorage.setItem(videoTime_${videoId}, video.currentTime); }); /script这个版本虽然简单但有几个明显问题频繁写入localStorage可能影响性能没有考虑多标签页同时操作的情况视频源变化时可能错误恢复进度2.2 关键参数说明currentTime视频当前播放位置秒可读可写timeupdate事件播放位置变化时触发频率约4-66HzlocalStorage浏览器本地存储容量约5MB实测发现在Chrome上timeupdate默认每秒触发4-8次Safari会更频繁些。这就引出了我们的第一个优化点。3. 性能优化实战3.1 事件节流控制直接监听timeupdate会导致存储操作太频繁。我曾在项目中遇到过因此导致的页面卡顿后来加了节流控制let lastSaveTime 0; const SAVE_INTERVAL 1000; // 每秒最多存一次 video.addEventListener(timeupdate, () { const now Date.now(); if (now - lastSaveTime SAVE_INTERVAL) { localStorage.setItem(videoId, video.currentTime); lastSaveTime now; } });更优雅的方式是用lodash的throttleimport { throttle } from lodash; video.addEventListener(timeupdate, throttle(() { localStorage.setItem(videoId, video.currentTime); }, 1000));3.2 存储策略优化localStorage是同步操作可能阻塞主线程。我们可以使用sessionStorage替代页面关闭后自动清除改用IndexedDB进行异步存储添加try-catch防止超出容量限制function safeSetStorage(key, value) { try { localStorage.setItem(key, value); } catch (e) { console.warn(本地存储已满自动清理旧数据); localStorage.clear(); localStorage.setItem(key, value); } }3.3 多视频管理当页面有多个视频时需要为每个视频创建唯一标识。我推荐这种方案video>window.addEventListener(storage, (event) { if (event.key videoId) { video.currentTime parseFloat(event.newValue); } });4.2 视频源变更处理当视频src变化时应该清除旧进度。我踩过这个坑const observer new MutationObserver(() { if (video.src ! lastVideoSrc) { localStorage.removeItem(videoId); lastVideoSrc video.src; } }); observer.observe(video, { attributes: true, attributeFilter: [src] });4.3 移动端适配技巧在iOS上自动播放受限且页面卸载时timeupdate可能来不及触发。解决方案添加页面可见性监听在beforeunload事件中强制保存// 页面隐藏时立即保存 document.addEventListener(visibilitychange, () { if (document.hidden) { localStorage.setItem(videoId, video.currentTime); } }); // 页面关闭前保存 window.addEventListener(beforeunload, () { localStorage.setItem(videoId, video.currentTime); });5. 替代方案对比5.1 各方案性能测试方案写入频率内存占用兼容性适用场景基础实现高频低最好简单页面节流控制1Hz低好大多数场景IndexedDB高频中较好复杂应用Service Worker缓存低频高一般PWA应用5.2 IndexedDB实现示例对于需要保存大量观看记录的应用IndexedDB是更好的选择// 初始化数据库 const dbPromise idb.open(VideoDB, 1, upgradeDB { upgradeDB.createObjectStore(progress); }); // 保存进度 async function saveProgress(videoId, time) { const db await dbPromise; const tx db.transaction(progress, readwrite); tx.objectStore(progress).put(time, videoId); return tx.complete; } // 读取进度 async function getProgress(videoId) { const db await dbPromise; return db.transaction(progress) .objectStore(progress).get(videoId); }6. 完整实现代码下面是我在一个在线教育平台实际使用的增强版代码包含了所有最佳实践video idlectureVideo>video.addEventListener(loadedmetadata, () { const savedTime localStorage.getItem(videoId); if (savedTime) { // 确保不会跳转到超过视频时长 video.currentTime Math.min(parseFloat(savedTime), video.duration); } });Q移动端失效怎么办AiOS的省电模式可能限制localStorage使用可以添加try-catch处理改用cookie作为fallback提示用户关闭省电模式8. 用户体验增强除了基础功能我还添加了这些提升体验的功能进度预览跳转前显示继续观看从XX分XX秒开始自动续播倒计时3秒内无操作则自动续播多设备同步配合后端API实现跨设备进度同步// 续播提示示例 function showResumePrompt(time) { const mins Math.floor(time / 60); const secs Math.floor(time % 60); const prompt document.createElement(div); prompt.innerHTML div classresume-prompt 上次观看到 ${mins}分${secs}秒 button idresumeBtn继续播放/button button idrestartBtn重新开始/button /div ; document.body.appendChild(prompt); document.getElementById(resumeBtn).addEventListener(click, () { video.currentTime time; prompt.remove(); }); document.getElementById(restartBtn).addEventListener(click, () { localStorage.removeItem(videoId); prompt.remove(); }); }实现视频断点续播看似简单但要做得健壮可靠需要考虑各种边界情况。经过多个项目的迭代现在这套方案已经能处理99%的使用场景。如果你有特殊需求比如需要保存播放速度、音量等状态也可以扩展这个模式。