给我的博客加上视频嵌入
我上一篇 iOS 上架 的文章里录了一段视频,挂在了 YouTube 和 Bilibili 上。当时我只能在 Markdown 里写两条普通的跳转链接,读者要点出去才能看,体验不太好。这次我顺手把视频嵌入这件事在博客里做了。
想要的效果
我的博客文章都是 .md 文件,没有用 MDX。所以我希望写法尽量简单,最好就是把视频 URL 单独放一行,构建的时候自动变成可以播放的视频。像这样:
这里有个视频:
https://www.youtube.com/watch?v=dQw4w9WgXcQ
不用引入组件,不用学新语法,存量的文章一行都不用改。
怎么实现的
Astro 处理 Markdown 是用 remark 这套工具链,我在 astro.config.mjs 里挂一个自己写的 remark 插件就可以了。
插件做的事情很简单:遍历 Markdown 的语法树(mdast),找到那种「一整段里只有一个 URL」的段落,把它替换成一个 iframe 的 HTML 节点。
核心的逻辑就这么几行:
export default function remarkVideoEmbed() {
return (tree) => {
tree.children = tree.children.map((node) => {
const url = extractSoleUrl(node);
if (!url) return node;
const html = urlToEmbed(url);
return html ? { type: 'html', value: html } : node;
});
};
}
这里有两个关键判断,决定了它「只在该出手的时候出手」:
extractSoleUrl只在「段落节点 + 子节点数量正好是 1 + 那个子节点是 link 或 text」时才返回 URL。换句话说,整段里只能有这一个 URL,正文里夹着的链接不会被误伤。urlToEmbed认不出来的链接(非 YouTube / Bilibili,或者格式不对)会返回空,节点原样保留。所以「自动变播放器」是一个安全的、失败即回退的转换。
支持的链接
YouTube 这边,几种常见写法我都按主机名 + 路径匹配了:
youtube.com/watch?v=ID、m.youtube.com/...、youtube-nocookie.com/...youtu.be/ID(短链)youtube.com/shorts/IDyoutube.com/embed/ID
Bilibili 匹配 /video/BVxxxxxxx 这个路径,所以标准链接和 b23.tv 短链都能认出来。
时间和分 P 参数也处理了:URL 里的 ?t= 起始时间、?p= 分 P。YouTube 的 ?t= 有 ?t=90 和 ?t=1m30s 两种格式,我写了个小函数统一转成纯秒数((\d+)h(\d+)m(\d+)s 和纯数字都兼容),再塞进 start 参数。
实现里的几个细节
这些是我特意处理过、对读者体验有实际影响的点:
- YouTube 默认走
youtube-nocookie.com,并带上rel=0。访客点播放之前不下发 cookie,对读者友好一点。 - Bilibili 默认关掉弹幕和自动播放(
danmaku=0、autoplay=0),同时开了high_quality=1。要不然一打开页面就有声音飘出来,挺扰民的。 - iframe 加了
loading="lazy",进入视口才加载,省点流量;还有referrerpolicy="strict-origin-when-cross-origin"和按平台收窄的allow(YouTube 才给clipboard-write、picture-in-picture这些)。 - 样式上用
aspect-ratio: 16 / 9,外面套一个.video-embed容器,自动按 16:9 显示,手机上看也不会变形。
效果
下面是我上一篇文章里那个视频,YouTube 和 Bilibili 各一个,可以直接点播放:
我源码里这两行就是赤裸裸的 URL,没有别的,构建的时候自动变成了上面的播放器。
一点感想
这个插件总共写了不到 100 行代码,但以后我写文章会舒服很多。
之前每次要嵌视频,我都得去 YouTube 或者 Bilibili 找 embed code,复制一段长长的 iframe HTML 进 Markdown,看着也乱。现在我直接粘 URL 就完事了,干净。这种「一次性写个小工具,把以后每篇文章的麻烦都省掉」的事,是我最愿意花时间做的——和我之前给博客做 动态 OG 图 是一个路子。