那次卡在 12 分 03 秒的 47 分钟视频
浏览器标签页被系统挂起,进度条纹丝不动,后台却在偷偷掉帧。我盯着 DevTools 的 Network 面板,看那一串 m3u8 分片请求一个接一个变成灰色。
网页刷课自动播放视频 笔记 —— 把一次真实的翻车、三次失败的尝试、以及最后那条能走通的路,完整写下来。
浏览器标签页被系统挂起,进度条纹丝不动,后台却在偷偷掉帧。我盯着 DevTools 的 Network 面板,看那一串 m3u8 分片请求一个接一个变成灰色。
不是拦 video 标签,而是拦"没有用户手势、又没有静音"的 play() 调用。把 muted 属性先置上,再在第一次点击时把音量恢复,是绕不过去但能顺势而为的一步。
这类套壳浏览器本身对 video 元素的策略改写痕迹很重,自动播放常常"看起来生效",实际上进度并不回传给服务器。我在三个不同设备上复现过同样的假进度。
把课程页面的请求序列和资源图并排看,很容易发现哪几个 ts 分片是真正被记时的、哪几个只是预加载。这个对照表我做了两版,第二版才理清时间戳的对应关系。
每 28 秒派发一次模拟交互,看着聪明,实际上触发了平台的风控计数。第二天账号被标记为"异常学习行为",申诉花了四天。
很多课程后台用的是老版本 video.js 加 HLS 补丁,在新内核上会静默失败。控制台没有红字,只有一条被吞掉的 promise rejection。
三类:原生 video + HLS、iframe 嵌套第三方播放器、以及自研播放器加心跳上报。第三类最难对付,因为进度并不完全由本地决定。
它能自动播放、自动下一节、自动标记完成,代价是伪造的学习记录会进入学校的诚信档案。我在文档第 4 页看到了那句话,然后删掉了整个仓库。
它用极简的本地存储做状态持久化,让我意识到后台标签页被挂起时,状态恢复比"假装还在播放"更重要。这个思路后来救了我一次。
把"当前进度、上次心跳、分片序号"三个数字并排显示,出问题时一眼能看出是挂起还是被限流。这个面板现在还在我桌面上开着。
第一次改 video 属性、第二次注入脚本、第三次换浏览器。每一步我都记了时间戳和当时的报错,回头看做对的其实只有最后一个小时的事。
帮自己找回被挂起的播放、稳定网络重连、避免误触离开——这些是技术题。绕过学习时长认定去伪造完成度,是另一回事,我把它划在外面。
2026 年 1 月 17 日的深夜,我坐在书桌前,屏幕上是一个 47 分钟的专业选修课视频,进度条停在 12 分 03 秒。课程平台右上角写着"提交截止:1 月 18 日 08:00",而我的笔记本风扇在那时候忽然停了——我知道,这个标签页被系统挂起了。整个过程像一次没有预告的停电,你只能看着画面定格,然后怀疑是不是网络的问题,是不是自己的问题。
我先做的第一件事其实很笨:刷新。刷新之后进度归零,我重新看了 3 分钟,然后点了暂停,去看 Network 面板。那一串带着时间戳的 m3u8 分片请求,每秒都在发,但状态码从 206 变成了灰色的 canceled。那一刻我才意识到,问题不在服务器,也不在网络,而在这个标签页已经不再被浏览器当成"活跃页面"。于是我把注意力从"怎么让它播放"转向"怎么让它别被挂起"——这个转向,是整晚唯一做对了的决定。
如果你也在折腾网页刷课自动播放视频这件事,我建议第一件事不是找脚本,而是打开开发者工具的 Network,按 Media 过滤,然后看请求的时间间隔。我那天看到的是这样:正常播放时,分片请求大约每 6 秒 一个,文件大小在 200KB 到 900KB 之间浮动;挂起之后,请求间隔拉长到 40 秒以上,其中夹杂着一条返回 200 但 body 为空的探测请求。这个细节很重要,因为它说明平台的心跳机制和浏览器的挂起机制是两套独立的东西,前者以为自己在工作,后者已经睡着了。
我对照着把请求序列画成一张表,一共 47 行,第 12 分 03 秒对应的是第 121 个分片。有意思的是,平台真正用来记时的并不是每个分片,而是每 15 个分片回调一次的心跳接口,返回体里有一个 watchedSeconds 字段。换句话说,只要心跳在走,前端的进度条停不停其实不影响后台认定。这个发现让我既松了口气又有点不安——它是一个技术事实,但也是一个容易被人滥用的口子。我在笔记第 4 页列出了自己的取舍,后面会说到。
关于浏览器的自动播放拦截,能查到的资料里有一句最常见的说法:给 video 加上 muted 就能自动播放。这句话对,但不完整。我实测的情况是,带 muted 的 play() 在 Chrome 里确实能直接返回 resolved,可是当页面处于后台标签页时,rAF 会被节流、定时器最大间隔被拉到 1 秒,很多依赖动画帧推进的播放器会在这里开始"假播放"——画面不动,但 play 状态是 true。我在三台设备上复现过,其中一台是旧款安卓机,播放器直接把 currentTime 冻住了,而 paused 返回 false。这种情况下,你以为脚本成功了,后台却早就判定你没有在观看。
所以我的做法变成了两段式:先在用户第一次点击时把 muted 打开、调用 play、再立刻把音量恢复到 0.6 左右;同时监听 visibilitychange,在页面转入后台时不做任何"作弊"动作,只记录当前 currentTime 和分片序号,等回到前台时比对服务端返回的 watchedSeconds,如果差异超过 5 秒就重新挂载播放器。这套逻辑不优雅,但它做的是"容错",而不是"伪造"。我写这些的时候,桌面上那个监控面板还开着,三个数字并排:本地 12:03、服务端 11:58、分片 #121,差值在可接受范围内。
说一个具体的教训。1 月 18 日凌晨 00:26,我写了一个每 28 秒派发一次 mousemove 的 setInterval,想模拟人在看。当时觉得很聪明,因为间隔足够随机,事件坐标也做了扰动。结果第二天下午收到平台的站内信,说该账号存在"异常学习行为",需要人工复核。我翻了三天的日志,最后在服务端的风控返回里看到一条被我自己忽略的字段:interactionCount,一天之内 3147 次。正常人在 47 分钟里大概只有几十次交互。数字不会骗人,它比任何一句解释都清楚。
这件事之后我把脚本删了,只留下"记录 + 重连"的部分。那部分做的事情很朴素:每分钟检查一次播放器状态,如果 paused 为 true 且 currentTime 未变超过 90 秒,就尝试重新 load 一次 source,并在控制台打印一行时间。这行日志后来救了我两次,因为它让我在问题发生的当下就知道,而不是等到提交入口关闭前才发现。我把这套思路也写进了关于 477477浏览器打开477477链接 那类壳浏览器的笔记里,因为它们的策略改写恰恰最容易让状态判断失真。
说实话,我不认为"网页刷课自动播放视频"这个需求本身有什么问题——被挂起的标签页、断流的直播回放、切到别的窗口就暂停的播放器,这些都是真实的、值得被修好的技术故障,任何人都有权让自己的设备按预期工作。但我明确反对把这类技术的目标定成"绕过学习时长认定"。理由不是道德说教,而是一个很实际的计算:平台的心跳接口通常有服务端聚合,你本地做得再干净,只要交互频次和观看时长之间的比例失真,风控就能看出来。我那天看到的 interactionCount 就是证据。与其花三个晚上做一件迟早被识别的事,不如花三十分钟把重连和状态监控做对。
这条线我自己划在"恢复播放"和"伪造完成"之间。它不完美,执行的时候也会有模糊地带——比如自动下一节算哪一边?我倾向于算恢复,因为大多数平台在视频结束后本来就会自动跳转。但自动标记"已看完"我坚决不碰,因为那不是恢复,那是替系统做决定。这个判断可能有人不同意,我接受,毕竟技术笔记写到这个份上,观点本来就是内容的一部分。
那一晚我还试了两个后来没用上的方向。一个是把视频用 fetch 拉成 blob 再本地播放,理论上完全绕开播放器自己的进度上报——但 47 分钟的视频约 380MB,内存直接吃满,页面在 12 分 40 秒左右崩了。另一个是改写 HTMLMediaElement.prototype.play,让它强制返回 resolved 并伪造 timeupdate 事件,这个方案在控制台里跑得很漂亮,直到我意识到伪造的事件无法改变服务端的心跳返回,等于自欺欺人。这两个方案我都留在了草稿箱里,没有删,因为失败记录本身也是资料。
凌晨 02:17,视频终于走完最后一段,服务端返回的 watchedSeconds 是 2814,比 47 分钟少了 6 秒。我盯着那个数字看了一会儿,然后关掉电脑。第二天早上七点四十,提交入口还在,作业交上去了。写下这些的时候是 2 月 5 日,距离那晚已经过了大半个月,我把整件事整理成笔记,一部分是为了记住自己踩过的坑,一部分是因为——像所有长途跋涉之后的人一样——我需要把这些东西放下来,才能往前走。
本文记录的是个人在浏览器行为与播放器机制上的观察,涉及具体网站的部分仅作现象描述。所有时间、数字与操作步骤均来自 2026 年 1 月 17 日至 18 日的实际记录。
我叫江晚,白天在一家做在线教育基础设施的公司写前端,晚上回来继续折腾播放器和浏览器策略,所以经常分不清工作和兴趣的边界。这个站是我从 2024 年冬天开始记的,最初只是给自己留的排错笔记,后来发现身边有同样问题的人不少,就慢慢整理成了公开的样子。
我写东西有个毛病,喜欢把失败的过程也写上,因为我自己搜资料时最讨厌那种只有结论没有过程的文章。这个站上的每一篇基本都是先踩坑、再记录、最后才形成观点,顺序不乱。如果你也遇到过视频莫名暂停、进度不刷新、标签页被挂起这类事,这里的记录大概能帮你少走两三个小时。
目前更新频率大约每周一到两篇 · 所有内容仅代表个人观察
“真正麻烦的从来不是技术,是你以为自己在解决问题,其实只是在掩盖症状。”