技术知识文章集合TECHNICAL ARCHIVE · 807 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTETC

为什么视频网站的视频链接地址是 blob?

所属分类
Other
文件格式
Markdown
原始路径
Other/26-为什么视频网站的视频链接地址是blob
本文目录10 个章节
  1. 从防盗链说起
  2. Blob 和 ArrayBuffer
  3. URL.createObjectURL
  4. 实战一:上传图片预览
  5. 实战二:以 Blob URL 加载网络视频
  6. HLS 和 MPEG DASH
  7. MediaSource:边下边播的核心
  8. Fragmented MP4(fmp4)
  9. 总结
  10. 整理者注

为什么视频网站的视频链接地址是 blob?

Category(分类): Other Status: 已读

原文:为什么视频网站的视频链接地址是blob?(CSDN,作者 wangzy2019,掘金同步版 juejin.im/post/5d1ea7a8e51d454fd8057bea)。 本文为整理版本,保留全部原理讲解与代码。

从防盗链说起

HTML5 的 video 标签让网页播放视频变得非常简单——src 指向真实视频地址即可。但这也意味着拿到地址就能随意下载(早期网站资源不怎么设防盗链)。

目前的云存储服务商大部分都支持 referer 防盗链:访问资源时请求头带上发起页面地址,判断其不存在(直接访问资源地址)或不在白名单内,即为盗链。

可是从某个时间开始,用调试工具看各大视频网站的 video src,它们统统变成了 blob:https://…… 的形式。这个 blob 是什么?

Blob 和 ArrayBuffer

最早是数据库用 Blob 存储二进制数据对象(不用关注存储格式)。在 Web 领域,Blob 对象表示一个只读原始数据的类文件对象——虽然是二进制数据但类似文件,可以像操作文件对象一样操作它。

ArrayBuffer 表示通用的、固定长度的原始二进制数据缓冲区:new ArrayBuffer(length) 分配一片连续内存,不能直接读写,需通过 TypedArray 或 DataView 视图来操作。TypedArray 常见类型:Int8/Uint8/Int16/Uint16/Int32/Uint32/Float32/Float64Array 等。

Blob 与 ArrayBuffer 的区别:除了原始字节以外,Blob 还提供了 mime type 作为元数据,两者可以互相转换。File 对象继承自 Blob,额外提供 name、lastModifiedDate 等元数据。

// Blob → 文本 / ArrayBuffer
const text = "<div>hello world</div>";
const blob = new Blob([text], { type: "text/html" }); // Blob {size: 22, type: "text/html"}

const textReader = new FileReader();
textReader.readAsText(blob);
textReader.onload = function () {
  console.log(textReader.result); // <div>hello world</div>
};

const bufReader = new FileReader();
bufReader.readAsArrayBuffer(blob);
bufReader.onload = function () {
  console.log(new Uint8Array(bufReader.result)); // Uint8Array(22) [60, 100, 105, ...]
};
// ArrayBuffer → Blob
const u8Buf = new Uint8Array([60, 100, 105, 118, ...]);
const u8Blob = new Blob([u8Buf], { type: "text/html" }); // 同样得到那个 Blob

URL.createObjectURL

video/audio/img 的 src,归根结底都是指向一个文件资源的地址。既然 Blob 可以当作文件用,那只要能生成一个指向 Blob 的地址,就可以用在 src 上——这就是 URL.createObjectURL():

const objectURL = URL.createObjectURL(object);
// blob:http://localhost:1234/abcedfgh-1234-1234-1234-abcdefghijkl

object 参数是 File、Blob 或 MediaSource 对象,生成以 blob: 开头的地址。要点:

  • localhost:1234 是当前页面的 location.host;
  • 即使是同样的二进制数据,每调用一次就得到一个不一样的 Blob URL;
  • URL 的存在时间等同于网页的存在时间,刷新或卸载即失效;
  • 用 URL.revokeObjectURL(objectURL) 主动释放,让浏览器可以垃圾回收;
  • 以 file:// 协议打开的 html,地址中的 origin 会变成 null 且无法直接访问。

实战一:上传图片预览

<input id="upload" type="file" />
<img id="preview" src="" alt="预览"/>
upload.onchange = function () {
  const file = upload.files[0];        // File 对象
  const src = URL.createObjectURL(file);
  preview.src = src;
};

同样适用于上传视频的预览。

实战二:以 Blob URL 加载网络视频

用 XHR/fetch 请求网络视频地址,把 responseType 设为 "blob",拿到 Blob 后生成 Blob URL:

function ajax(url, cb) {
  const xhr = new XMLHttpRequest();
  xhr.open("get", url);
  xhr.responseType = "blob"; // ""|"text"-字符串 "blob"-Blob对象 "arraybuffer"-ArrayBuffer
  xhr.onload = function () { cb(xhr.response); };
  xhr.send();
}

ajax('video.mp4', function (res) {
  video.src = URL.createObjectURL(res);
});

表面上看和各大视频网站形式一致了。但问题在于:这种形式要等全部视频数据请求完才能播放——小视频还行,大视频就爆炸了,视频网站不可能这么干。解决方案就是流媒体。

HLS 和 MPEG DASH

  • HLS(HTTP Live Streaming):Apple 实现的基于 HTTP 的媒体流传输协议。以 ts 为传输格式、m3u8 为索引文件(包含所用 ts 文件名、时长等信息)。移动端大部分浏览器支持(video 直接加载 m3u8),PC 端除 Safari 外需引入库。优酷等用此方案——调试工具 Network 里能看到 m3u8 文件和每隔一段时间的 ts 请求;
  • MPEG DASH(Dynamic Adaptive Streaming over HTTP):类似 HLS 的动态码率流媒体技术,通过 MPD 将视频内容切片,每个切片有多个码率,客户端按网络情况选择并支持无缝切换。YouTube、B 站用此方案——索引文件是 mpd,传输格式推荐 fmp4(扩展名 .m4s 或 .mp4),所以 B 站播放时网络请求里每隔一段时间有几个 m4s 文件。

相关阅读:B 站官方文章《我们为什么使用DASH》(bilibili.com/read/cv855111)。

MediaSource:边下边播的核心

不管 HLS 还是 DASH,抛开索引文件解析,核心问题只有一个:如何把多段视频数据合并、让 video 标签无缝播放。

改 src 切换视频显然不能无缝。基于 Blob URL 的思路:用 Blob URL 指向一个"视频数据容器",不断把下一段视频的二进制数据追加进去——这就是 MediaSource:一个媒体数据容器,可与 HTMLMediaElement 绑定。通过 URL.createObjectURL(mediaSource) 生成 Blob URL 设置到 video.src,播放过程中用 sourceBuffer.appendBuffer() 往容器里追加数据。

const video = document.querySelector('video');
// 假设有 init.mp4 + video1.mp4 ~ video5.mp4 五个分段
const assetURL = "http://www.demo.com";
// 视频格式和编码信息,用于判断浏览器是否支持
const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';

if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
  const mediaSource = new MediaSource();
  video.src = URL.createObjectURL(mediaSource); // 生成 Blob URL 绑定 video
  mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
  console.error('Unsupported MIME type or codec: ', mimeCodec);
}

function sourceOpen() {
  const mediaSource = this;
  const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  let i = 1;
  function getNextVideo(url) {
    // ajax 代码见上文,responseType 为 arraybuffer
    ajax(url, function (buf) {
      sourceBuffer.appendBuffer(buf); // 追加数据,不影响当前播放
    });
  }
  sourceBuffer.addEventListener("updateend", function () {
    if (i === 1) video.play();                 // 初始化视频加载完即播放
    if (i < 6) getNextVideo(`${assetURL}/video${i}.mp4`); // 加载下一段
    if (i === 6) {
      mediaSource.endOfStream();               // 全部加载完关闭容器
      URL.revokeObjectURL(video.src);          // 释放 Blob URL
    }
    i++;
  });
  getNextVideo(`${assetURL}/init.mp4`);
}

至此就实现了一个简易的流媒体播放功能。再加上 m3u8/mpd 解析和 UI,就是一个流媒体播放器。

Fragmented MP4(fmp4)

一个常见的坑:跑 MDN 的 MediaSource 示例,用官方视频没问题,换自己的 mp4 就报错——因为 MediaSource 需要的是 fmp4(Fragmented MP4),是特殊的 mp4 文件。

  • 普通 mp4 是嵌套结构,必须从头加载才能完整播放,不能从中间一段开始;
  • fmp4 由一系列片段组成,服务器支持 byte-range 请求时,片段可独立请求播放,无需加载整个文件。

可用这个网页判断 mp4 是否为 fmp4:http://nickdesaulniers.github.io/mp4info/

转换与切割(FFmpeg 或 Bento4,均为命令行工具):

# Bento4 mp4fragment
mp4fragment video.mp4 video-fragmented.mp4

# FFmpeg(注意 default_base_moof 参数:网上大部分资料不带它,虽能转换成功,
# 但经测试不加此参数网页中 MediaSource 处理视频会报错)
ffmpeg -i video.mp4 -movflags empty_moov+default_base_moof+frag_keyframe video-fragmented.mp4

# Bento4 切割分段
mp4split video.mp4 --media-segment video-%llu.mp4 --pattern-parameters N

总结

视频网站的 blob: 链接 = URL.createObjectURL(MediaSource):MSE 作为可追加数据的容器生成 Blob URL 给 video.src,配合 HLS/DASH 流媒体协议(m3u8/mpd 索引 + ts/fmp4 分片)实现边下边播,同时隐藏了真实视频分片地址。防盗链 + 流式播放 + 防直接下载,一举三得。

整理者注

本文与本分类《你不知道的 Blob》互为补充:那篇讲 Blob 本身的 API 与通用场景,本文讲 Blob URL + MediaSource 在视频领域的深度应用。另见《如何下载视频资源以blob:http开头的资源》——从"下载者"视角理解同一套机制。

807 DOCUMENTS · 12 COLLECTIONS
ARCHIVE SEARCH807 篇文章