如何用 DOM 控制 bilibili / YouTube 页面里的视频(2026 版)
Sawana Huang - Fri Aug 01 2025
重新整理如何在网页里拿到真正的 HTMLVideoElement,控制播放、暂停、跳转和倍速,并说明什么时候 `querySelector(\"video\")` 就够了,什么时候该换成更稳的做法。
这篇原本只是我在做浏览器插件时记下的一条发现:很多网页播放器最终都会落到一个 video 元素上,只要拿到它,就能直接控制播放状态。
这个思路现在依然成立,但原文讲得太快了。放到 bilibili、YouTube 这种真实页面里,document.querySelector("video") 经常只是第一步,还不一定就是你真正想控制的那个播放器。
所以这次我把文章重写成一条更稳的实践线:
- 什么时候直接拿
<video>就够了 - 什么时候要先筛选出“当前页面真正正在用的那个视频”
- 什么时候已经不该继续硬控 DOM,而该换成平台自己的 API
先说结论
如果当前页面里已经有一个可访问的 HTMLVideoElement,那么这些操作通常都成立:
play()pause()currentTime = 92playbackRate = 1.5volume = 0.5
但有三个前提最好先记住:
document.querySelector("video")只会返回第一个匹配元素- 真实页面里可能同时存在多个
<video> - 如果视频在
iframe里,或者页面是后来动态插入播放器,你要多走一步
为什么这件事能成立
MDN 对 HTMLMediaElement 和 HTMLVideoElement 的定义很清楚:网页里的标准视频元素本身就暴露了播放、暂停、跳转、倍速、音量、事件监听这些能力。
换句话说,你想做的“空降到 1 分 32 秒”“自动暂停”“记录观看进度”,本质上往往只是对视频元素的属性和方法做一次编程控制。
最小例子就是这一句:
document.querySelector("video")?.currentTime = 92;它在很多页面上都能工作,但复杂页面里别急着把它当成最终答案。
querySelector("video") 有用,但别把它理解得太满
document.querySelector("video") 的含义很简单:返回文档中第一个匹配到的 <video> 元素。
这在下面这些场景里通常够用:
- 页面里只有一个视频播放器
- 你是在浏览器控制台里做一次临时调试
- 你只想快速验证“这个站用的是不是 HTML5 视频播放器”
但 bilibili、YouTube 这类站点更常见的情况是:
- 页面里可能不止一个
<video> - 广告、预览、背景视频也可能占掉第一个匹配项
- 单页应用切路由后,旧节点会被替换
- 你的脚本执行时,播放器可能还没插入 DOM
所以更稳的思路是:先找出页面里有哪些 video,再挑出你真正想控制的那个。
一组更稳的选择逻辑
如果你只是做自己的学习工具、浏览器脚本或扩展,我更建议先写一个辅助函数:
function pickMainVideo(root: ParentNode = document): HTMLVideoElement | null {
const videos = Array.from(root.querySelectorAll("video"));
if (videos.length === 0) return null;
return (
videos
.filter((video) => video.readyState >= 1)
.sort(
(a, b) =>
b.clientWidth * b.clientHeight - a.clientWidth * a.clientHeight,
)[0] ?? videos[0]
);
}这段逻辑做了两件事:
- 优先选择已经加载过元数据的
video - 如果页面里有多个视频,优先挑面积更大的那个
这条规则当然也有局限,但已经比“永远拿第一个 video”更接近真实需求。
你可以先这样验证:
const video = pickMainVideo();
console.log(video);
console.log(video?.currentTime);
console.log(video?.duration);最常用的几种控制方式
只要拿到 HTMLVideoElement,最常用的操作基本就是下面这些。
1. 播放和暂停
const video = pickMainVideo();
await video?.play();
video?.pause();这里有个细节要注意:play() 返回的是 Promise。如果浏览器认为当前调用不满足自动播放策略,它会直接 reject。
2. 跳到指定时间点
const video = pickMainVideo();
if (video) {
video.currentTime = 92;
}这就是“跳到 1 分 32 秒”的核心做法。很多“空降链接”“回到上次观看位置”“点击字幕跳转”最后都会落到这里。
3. 调整倍速和音量
const video = pickMainVideo();
if (video) {
video.playbackRate = 1.5;
video.volume = 0.5;
}做学习工具、刷课插件、速记播放器时,这两个属性通常是最先用到的。
4. 监听播放进度
const video = pickMainVideo();
video?.addEventListener("timeupdate", () => {
console.log("current time:", video.currentTime);
});如果你想实现“自动保存看到哪里了”,这一类事件监听比只在按钮点击时读一次时间更可靠。
5. 请求全屏
const video = pickMainVideo();
await video?.requestFullscreen();这属于元素级全屏,不等于网站自己的“影院模式”或“网页全屏”。不同站点会在原生全屏外再包一层自己的播放器状态。
做“空降链接”时,关键不在链接本身
原文里提到 bilibili 视频空降链接,这个方向本身没问题,只是实现重点需要换个说法。
如果你是自己做一个视频学习工具,所谓“空降链接”通常包含三步:
- 从 URL、笔记卡片或按钮里拿到目标秒数
- 等视频元数据加载完成
- 把
video.currentTime设到对应位置
一个更接近实战的版本如下:
function jumpTo(video: HTMLVideoElement, seconds: number) {
if (video.readyState >= 1) {
video.currentTime = seconds;
return;
}
video.addEventListener(
"loadedmetadata",
() => {
video.currentTime = seconds;
},
{ once: true },
);
}
const video = pickMainVideo();
if (video) {
jumpTo(video, 92);
}这样写的好处是:播放器还没完全准备好时,也不会因为 duration 或元数据还没到位而让逻辑显得飘。
浏览器扩展里更常踩的两个坑
我自己后来回头看,这篇最值得补的其实是这部分。
1. 你的脚本执行得太早了
现代视频网站很多都是客户端渲染。content script 先跑了,播放器后出现,这时你会以为“怎么选不到 video”。
最简单的补法有两个:
- 在合适的时机重试
- 用
MutationObserver等待播放器节点进入页面
例如:
function waitForVideo(timeout = 10000): Promise<HTMLVideoElement> {
return new Promise((resolve, reject) => {
const existing = pickMainVideo();
if (existing) {
resolve(existing);
return;
}
const observer = new MutationObserver(() => {
const next = pickMainVideo();
if (!next) return;
observer.disconnect();
resolve(next);
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
window.setTimeout(() => {
observer.disconnect();
reject(new Error("Timed out while waiting for video"));
}, timeout);
});
}2. 你控制的是父页面,但视频其实在 iframe 里
这在 YouTube embed 场景里很常见。
如果视频播放器在当前文档的 iframe 里,父页面直接 document.querySelector("video") 是拿不到 iframe 内部那个元素的。这个时候你要么在 iframe 自己的上下文里执行脚本,要么直接使用平台提供的 API。
YouTube 有一类场景更适合直接用官方 IFrame API
如果你是在自己的页面里嵌入 YouTube 视频,想长期稳定地控制播放器,那么官方 IFrame Player API 更合适。
原因很直接:
- 你的页面里看到的其实是一个
<iframe> - 你真正想控制的是 iframe 里面的播放器
- 官方 API 已经提供了
playVideo()、pauseVideo()、seekTo()这类控制方法
这类场景下,继续围着 querySelector("video") 转,维护成本通常更高。
我现在会怎么总结这件事
如果你只是想在 bilibili、YouTube 这类网页里研究“播放器时间如何跳转”“视频怎么暂停”“如何记住上次看到哪”,你的第一站依然应该是 HTMLVideoElement。
但文章写到这里,我会把经验压成三句话:
- 先确认页面里有没有你能访问到的
<video> - 别默认第一个
video就是目标播放器 - 一旦进入
iframe或平台封装更重的场景,就优先考虑官方 API
这样理解以后,再去做浏览器扩展、学习工具、自动化脚本,心里会稳很多。
参考资料
- MDN: Document.querySelector()
- MDN: HTMLMediaElement.currentTime
- MDN: HTMLMediaElement.play()
- MDN: HTMLMediaElement.playbackRate
- MDN: HTMLMediaElement.volume
- MDN: Element.requestFullscreen()
- MDN: HTMLMediaElement loadedmetadata event
- MDN: MutationObserver
- YouTube IFrame Player API Reference