给我的博客加上视频嵌入

我上一篇 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=IDm.youtube.com/...youtube-nocookie.com/...
  • youtu.be/ID(短链)
  • youtube.com/shorts/ID
  • youtube.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=0autoplay=0),同时开了 high_quality=1。要不然一打开页面就有声音飘出来,挺扰民的。
  • iframe 加了 loading="lazy",进入视口才加载,省点流量;还有 referrerpolicy="strict-origin-when-cross-origin" 和按平台收窄的 allow(YouTube 才给 clipboard-writepicture-in-picture 这些)。
  • 样式上用 aspect-ratio: 16 / 9,外面套一个 .video-embed 容器,自动按 16:9 显示,手机上看也不会变形。

效果

下面是我上一篇文章里那个视频,YouTube 和 Bilibili 各一个,可以直接点播放:

我源码里这两行就是赤裸裸的 URL,没有别的,构建的时候自动变成了上面的播放器。

一点感想

这个插件总共写了不到 100 行代码,但以后我写文章会舒服很多。

之前每次要嵌视频,我都得去 YouTube 或者 Bilibili 找 embed code,复制一段长长的 iframe HTML 进 Markdown,看着也乱。现在我直接粘 URL 就完事了,干净。这种「一次性写个小工具,把以后每篇文章的麻烦都省掉」的事,是我最愿意花时间做的——和我之前给博客做 动态 OG 图 是一个路子。