拖进度条=发一个带 Range 的请求,服务器回 206 加一段字节。 服务器要会三件事,少一件就拖不动。
8.3.2 对比了两种服务器的整体行为,这一节把「拖进度条」这一件事拆到底。 它牵扯的是服务器那三个能力,不是播放器的问题。
一、拖一下,发生了什么
从文件中间开始要一段,实测结果:
项 值
文件总字节 48741 请求的起点 24370 响应码 206 Partial Content Content-Range bytes 24370-48740/48741 实际收到体 24371 字节
起点 24370 是「文件一半」,收到 24371 字节,两个数加起来正好 48741。 一个字节没多要,也一个字节没少给。
Content-Range 那一行的斜杠后面是总长,客户端靠它算进度条的比例。
没有这一行,它只知道「拿到了一段」,不知道「这一段在整体里的哪」。
二、服务器要会的三件事
这三件事缺一件,进度条就拖不动:
能力 不做的后果
认 Range 头 永远从头开始,拖了也白拖 回 206 与 Content-Range 客户端不知道这段是哪一段 回 Accept-Ranges: bytes 客户端不敢发 Range
第三件最容易被忽略:Accept-Ranges: bytes 是个「广告」。
它告诉客户端「我支持分段,你尽管要」。不发的话,
保守的客户端会默认「这服务器不支持」,于是老老实实从头下。
VLC 当 HTTP 服务器时会发这个头,所以浏览器与播放器都敢拖。
Python 自带的 http.server 从 3.7 起支持 Range,
但它不发 Accept-Ranges——这就是「能用」与「敢用」的差别。
顺带提醒:Range 还分单段与多段。
bytes=0-99,200-299 这种多段请求要回 multipart/byteranges,
手写服务器基本都不支持,实践中也很少有人用。
三、直播为什么拖不动
因为直播的「后半段」还没生成。 服务器手里没有那段字节,给不出来。
客户端要什么 服务器能怎么做
往后的部分 等它生成,现在没有 往前的部分 可能已经扔了(直播不留档) 当前这一段 给,这是唯一能给的
所以直播播放器的进度条都是灰的。 这不是产品设计,是协议层面就没有那段时间的数据。
想「回看」就必须有人把流录下来(也就是 DVR)—— 一旦录了,它就不是纯直播了,而是「直播加一段存储」, 那段存储可以按 Range 读,于是进度条又活了。
这就是为什么有些直播能拖回五分钟前:后端存了一份, 播放器拖的时候读的是存储,不是直播流。
四、VLC 当服务器的两条命令
VLC 当 HTTP 服务器有两条常用命令:
vlc s7.mp4 --sout="#std{access=http,mux=ts,dst=:8080}"
vlc s7.mp4 --sout="#http{mux=ffmpeg{mux=flv},dst=:8080/live.flv}"
命令 出来的东西 适合
access=http mux=ts 一路 TS 简单、能播 http mux=flv 一路 FLV 延迟更低
第一条最省事:浏览器与 ffplay 都能直接播这个地址。 第二条走 FLV,延迟能压到一两秒,代价是有些平台不认。
注意这两条都是「推」型:VLC 一直在发,观众连上来从头看还是从中间看, 取决于它是不是把已发出的数据留在缓冲里。 真直播一般不留,所以也是「从当下开始」。
五、动手 Demo:从中间要一段
下面这段代码手写一个带 Range 的请求,从文件一半开始要, 量响应码、Content-Range 与实际收到的字节数。
import http.server, os, socket, threading
class H(http.server.BaseHTTPRequestHandler):
u"""最小 HTTP 服务:只认 Range,回 206 加 Content-Range。"""
def log_message(self, *a):
pass
def do_GET(self):
p = os.path.join(os.getcwd(), "s7.mp4")
n = os.path.getsize(p)
rng = self.headers.get("Range")
start, code, cnt = 0, 200, n
if rng:
start = int(rng.split("=")[1].split("-")[0] or 0)
code, cnt = 206, n - start
self.send_response(code)
self.send_header("Content-Length", str(cnt))
if code == 206:
self.send_header("Content-Range",
"bytes %d-%d/%d" % (start, n - 1, n))
self.end_headers()
with open(p, "rb") as f:
f.seek(start)
self.wfile.write(f.read(cnt))
srv = http.server.ThreadingHTTPServer(("127.0.0.1", 8000), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
size = 48741
start = size // 2
req = (u"GET /s7.mp4 HTTP/1.1\r\n"
u"Host: x\r\n"
u"Range: bytes=%d-\r\n\r\n" % start)
s = socket.create_connection(("127.0.0.1", 8000), 3.0)
s.sendall(req.encode())
buf = b""
while True:
d = s.recv(65535)
if not d:
break
buf += d
s.close()
head, body = buf.split(b"\r\n\r\n", 1)
print(head.decode("utf-8", "replace").split("\r\n")[0])
print(u"收到体 %d 字节,起点 %d,总长 %d" % (len(body), start, size))
print(u"加起来 = %d" % (len(body) + start))
srv.shutdown()
srv.server_close()
实跑输出:
--------------------------------------------------------------------
8.3.3 VLC 作为 HTTP 流媒体服务器
--------------------------------------------------------------------
**① 拖进度条 = 发一个带 Range 的请求**
项 值
---------------------------------------------
文件总字节 48741
请求的起点 24370
响应码 206 Partial Content
Content-Range bytes 24370-48740/48741
实际收到体 24371 字节
**② 服务器要会三件事才叫「能拖进度条」**
能力 不会的后果
--------------------------------------------------------
认 Range 头 永远从头开始,拖了也白拖
回 206 与 Content-Range 客户端不知道这段是哪一段
回 Accept-Ranges: bytes 客户端不敢发 Range
**VLC 当 HTTP 服务器时会回 `Accept-Ranges: bytes`,所以浏览器/播放器才敢拖。**
**Python 那个 `http.server` 从 3.7 起也支持 Range,但它不发 `Accept-Ranges`。**
**③ 直播流为什么不能拖**
**因为直播的「后半段」还没生成,服务器没有那段字节可以给。**
**硬要拖,只能给一个 416(范围不满足),或者干脆忽略。**
**所以所有直播播放器的进度条都是「不可拖」的——**
**这不是产品设计,是协议层面就没有那段时间的数据。**
起点 24370、收到 24371 字节,加起来正好 48741——一个字节不差。 这就是 Range 的语义:给你一段,长度与起止都由你指定。
如果服务器不支持 Range,这条请求也会得到 200 加整个文件。 那时收到的体是 48741 字节而不是 24371——客户端要能分辨这两种。
判断方法就是看状态码:206 是分段,200 是整份。 播放器看到 200 就知道「拖不了」,于是把进度条变灰。
30 秒实验
- 只要一个字节:
Range: bytes=100-100,看是不是回 206 加 1 字节。 - 越界请求:
Range: bytes=99999-,看回 416 还是 200。 - 多段请求:试
bytes=0-9,20-29,看服务器怎么处理。
常见误区
- 以为支持 Range 就够了。还要发 Accept-Ranges,客户端才敢用。
- 以为直播能拖回去。没存储就没那段数据。
- 把多段 Range 当常规操作。手写服务器基本都不支持。
自测三问(点开对答案)
1. 拖进度条在协议上是什么? —— 发一个带 Range 头的 GET,服务器回 206 加那一段字节。 2. Accept-Ranges 有什么用? —— 它是广告,告诉客户端「我支持分段」,客户端才敢发 Range。 3. 为什么有些直播能拖回五分钟? —— 后端录了一份,拖的时候读的是存储而不是直播流。- ☐ (本节完,下一节 8.3.4 FFmpeg 实现 HTTP 直播拉流)