揭秘B站视频秒播技术:m4s与SourceBuffer的奥秘

为什么 b 站视频播放的那么快

m4s 分段存储视频,通过 range 请求动态下载某个视频片段,然后通过 SourceBuffer 来动态播放这个片段。

我们分析了 b 站、知乎视频播放速度很快的原因。

结论是通过 range 动态请求视频的某个片段,然后通过 SourceBuffer 来动态播放这个片段。

这个 range 是提前确定好的,会根据进度条来计算下载哪个 range 的视频。

播放的时候,会边播边下载后面的 range,而调整进度的时候,也会从对应的 range 开始下载。

服务端存储这些视频片段的方式,b 站使用的 m4s,当然也可以用 m3u8,或者像知乎那样,动态读取 mp4 文件的部分内容返回。

除了结论之外,调试过程也是很重要的:

我们通过 status-code 的过滤器来过滤出了 206 状态码的请求。

通过自定义列在列表中直接显示了 Content-Range:

通过 command + f 搜索了响应的内容:

range 请求

Content-Range 头部的作用

标识返回的字节范围

格式:Content-Range: bytes -/

示例:

Content-Range: bytes 13965476-14514678/44620616

:13,965,476(返回数据的起始字节偏移量,从 0 开始)。:14,514,678(返回数据的结束字节偏移量)。:44,620,616(文件的总大小,单位:字节)。

返回的数据长度

实际返回的数据长度为 end - start + 1,即 14514678 - 13965476 + 1 = 549,203 字节。

与 Range 请求的协作流程

客户端发起 Range 请求

示例请求:

GET /video.mp4 HTTP/1.1

Range: bytes=13965476-14514678

服务器响应 206 Partial Content

服务器检查文件是否存在、

Range

是否有效,并返回:

状态码 206(部分内容)。Content-Range 头部描述返回范围。Content-Length 头部描述返回数据的实际大小。

示例响应:

HTTP/1.1 206 Partial Content

Content-Range: bytes 13965476-14514678/44620616

Content-Length: 549203

Content-Type: video/mp4

客户端处理响应

播放器或下载工具根据 Content-Range 更新进度或拼接数据流。

Range服务器支持

(1) 服务器支持

必须支持 Range 请求

服务器需正确处理 Range 头部并返回 206 状态码。若不支持,可能返回 200 OK 和整个文件。

配置示例(Nginx)

location / {

add_header 'Accept-Ranges' 'bytes'; # 声明支持 Range 请求

root /var/www/html;

}

(2) 字节范围有效性

边界检查

服务器需验证请求的 是否在文件范围内。若越界,可返回 416 Requested Range Not Satisfiable。

示例错误响应:

HTTP/1.1 416 Requested Range Not Satisfiable

Content-Range: bytes */44620616

(3) 多范围请求(Multi-Range)

支持多个范围

客户端可通过 Range: bytes=0-999,2000-2999 请求多个不连续的范围。

服务器需返回 multipart/byteranges 类型的响应,每个部分包含自己的 Content-Range 头部。

示例响应:

HTTP/1.1 206 Partial Content

Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5

--3d6b6a416f9b5

Content-Type: video/mp4

Content-Range: bytes 0-999/44620616

[0-999字节的数据]

--3d6b6a416f9b5

Content-Type: video/mp4

Content-Range: bytes 2000-2999/44620616

[2000-2999字节的数据]

--3d6b6a416f9b5--

(4) 缓存控制

缓存部分内容:

服务器可通过Cache-Control头部控制Range响应的缓存行为。例如:

http

Cache-Control: public, max-age=3600

浏览器或 CDN 可能缓存 206 响应,但需注意缓存一致性(如文件更新时需失效缓存)。

调试与验证

使用 curl 测试

bash

curl -I -H "Range: bytes=13965476-14514678" https://example.com/video.mp4

预期响应:

HTTP/1.1 206 Partial Content

Content-Range: bytes 13965476-14514678/44620616

浏览器开发者工具

在 Network 面板中查看视频片段请求的 Range 头部和响应的 Content-Range 头部。

SourceBuffer中的视频数据存储位置

使用SourceBuffer处理视频时,视频数据并不会直接“保存”到传统意义上的本地文件系统路径中,而是存储在内存中,通过浏览器提供的机制进行管理和操作,具体分析如下:

SourceBuffer中的视频数据存储位置

内存中的临时存储:SourceBuffer是MediaSource API的一部分,用于在内存中动态构建媒体数据流。它接收音视频片段(MediaSegment)并通过appendBuffer()方法将这些片段添加到缓冲区中。这些数据不会直接写入硬盘,而是以二进制形式暂存于内存,供

数据生命周期与释放机制

内存释放:SourceBuffer中的数据在播放过程中持续占用内存,直至被显式移除或页面卸载。通过remove()方法可清理指定时间范围内的片段,或调用abort()终止所有操作。关闭页面或刷新时,内存中的数据会自动释放。无持久化存储:由于数据仅存在于内存,页面关闭后无法直接恢复。若需长期保存,需通过MediaRecorder API录制

与本地存储的区别

非文件系统存储:SourceBuffer不涉及硬盘文件操作,所有数据均通过JavaScript动态操作。若需将视频保存到本地,需结合MediaRecorder或文件系统访问API(如Chrome的showSaveFilePicker)实现。实时处理特性:其设计初衷是支持流式播放和动态调整(如自适应码率),而非持久化存储。数据在内存中的高效管理确保了低延迟播放,但牺牲了长期存储能力。

获取视频和音频url

代码

public class Demo {

public static void main(String[] args) {

//保存路径

String savePath = "E:\\";

//视频处理

String videoUrl1 = "https://cn-hncs-cu-01-09.bilivideo.com/upgcxcode/47/98/1044339847/1044339847-1-30077.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEqxTEto8BTrNvN0GvT90W5JZMkX_YN0MvXg8gNEV4NC8xNEV4N03eN0B5tZlqNxTEto8BTrNvNeZVuJ10Kj_g2UB02J0mN0B5tZlqNCNEto8BTrNvNC7MTX502C8f2jmMQJ6mqF2fka1mqx6gqj0eN0B599M=&tag=&nbs=1&oi=2748115477&mid=476134903&gen=playurlv3&og=hw&deadline=1746870842&platform=pc&trid=0000300b3d6e4130479cb8e8b369ca44b4bu&os=bcache&uipk=5&upsig=da32130624c313a1e7cc066d25375cef&uparams=e,tag,nbs,oi,mid,gen,og,deadline,platform,trid,os,uipk&cdnid=34209&bvc=vod&nettype=0&bw=323854&f=u_0_0&agrr=1&buvid=CFA17265-C236-754B-2EAB-19F445F8089E87661infoc&build=0&dl=0&orderid=0,3";

// 文件名称 建议和链接保持一致

String name1 = "1044339847-1-30077.m4s";

downloadMovie(videoUrl1, savePath, name1);

//音频处理

String videoUrl2 = "https://cn-hncs-cu-01-09.bilivideo.com/upgcxcode/47/98/1044339847/1044339847_nb3-1-30280.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEqxTEto8BTrNvN0GvT90W5JZMkX_YN0MvXg8gNEV4NC8xNEV4N03eN0B5tZlqNxTEto8BTrNvNeZVuJ10Kj_g2UB02J0mN0B5tZlqNCNEto8BTrNvNC7MTX502C8f2jmMQJ6mqF2fka1mqx6gqj0eN0B599M=&platform=pc&trid=0000300b3d6e4130479cb8e8b369ca44b4bu&mid=476134903&tag=&uipk=5&gen=playurlv3&os=bcache&oi=2748115477&deadline=1746870842&nbs=1&og=cos&upsig=42018fc6e33142ec3085bbf058137512&uparams=e,platform,trid,mid,tag,uipk,gen,os,oi,deadline,nbs,og&cdnid=34209&bvc=vod&nettype=0&bw=137033&build=0&dl=0&f=u_0_0&agrr=1&buvid=CFA17265-C236-754B-2EAB-19F445F8089E87661infoc&orderid=0,3" ;

// 文件名称 建议和链接保持一致

String name2 = "1044339847_nb3-1-30280.m4s";

downloadMovie(videoUrl2, savePath, name2);

// ffmpeg -i E:\1044339847-1-30077.m4s -i 1044339847_nb3-1-30280.m4s -codec copy E:\video.mp4

}

public static void downloadMovie(String BLUrl, String savePath, String fileName) {

InputStream inputStream = null;

try {

URL url = new URL(BLUrl);

URLConnection urlConnection = url.openConnection();

urlConnection.setRequestProperty("Referer", "https://www.bilibili.com/video/BV1j8411N7Bm"); // 填需要爬取的bv号

urlConnection.setRequestProperty("Sec-Fetch-Mode", "no-cors");

urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36");

urlConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");

urlConnection.setConnectTimeout(10 * 1000);

inputStream = urlConnection.getInputStream();

} catch (IOException e) {

e.printStackTrace();

}

File file = new File(savePath+fileName);

int i = 1;

try {

BufferedInputStream bis = new BufferedInputStream(inputStream);

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));

byte[] bys = new byte[1024];

int len = 0;

while ((len = bis.read(bys)) != -1) {

bos.write(bys, 0, len);

}

bis.close();

bos.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

合并视频和音频

ffmpeg -i E:\1044339847-1-30077.m4s -i 1044339847_nb3-1-30280.m4s -codec copy E:\video.mp4

播放视频

播放软件:PotPlayer