精选列表里放着一个台,URL 看着没毛病,点进去转圈。这种源我遇到得多了,最后搞清楚一件事:HTTP 状态码说明不了能不能播。
200 会撒谎
不少源会返回 200,但正文根本不是音频。可能是 JSON 错误信息,也可能是一个漂亮的 HTML 错误页——服务器挂了,状态码照样 200。
所以探测得读正文。我的规则是读前 2048 个字节,看它到底像什么。
探测看什么
先发一个普通 GET,不带 Range,模拟真实播放。读够 2048 字节就断开,最多等 4 秒——直播流会一直推数据,不主动断就卡住了。
判断规则:
- 正文以
{或[开头,或Content-Type带json:死。 - 正文是 HTML(
<!doctype html>、<html>):死。 - URL 带
.m3u8或正文含#EXTM3U:算 HLS,但还要再查一层——播放列表里至少要有一条非注释的地址,光有#EXTM3U没用。 - 剩下的才算能播。
有个坑是 gzip。有的源(新城电台走 cdn77)把 m3u8 压缩了返回,不解压看到的是一团乱码,会被误判成「不是播放列表」。所以先看 Content-Encoding,是 gzip 就先解开再判断。
Range 那次翻车
最早我按常规思路来:探测就发 Range: bytes=0-2047,只要前 2KB,省流量。
结果蜻蜓的 CDN 对 Range 请求回 404,普通 GET 却能正常出音频。一批本来能播的台,被我自己判死了。
现在反过来:普通 GET 优先,Range 只在普通 GET 失败、而且这个 URL 不是 HLS 的时候兜底——探测模拟的是真实播放。
两轮探测
精选列表不是抓下来就用。重建的时候,每个 URL 要过两轮全量探测,两轮都过才算数;中间某一轮抽风(超时、限流)的台,直接不要。
采编来源是蜻蜓、央广网、tingfm、radio5 的公开页面,抓完按 URL 和规范化台名去重,一个台只留一条,最后 409 个。
两件事值得单独说。
403 不能当死链。 央广 ytlive 的链接带 key 和 time,大约两周过期,过期后探测拿到 403。但这不是台死了,是签名过期,换个新签就行。要是因为一个 403 就把整张表删了,那才叫出事。
探测本身也会挂。 清理死链的工具里有个保险:如果要删的台超过一半,直接中止,不写回。这种情况多半是探测环境出了问题,不是列表坏了。
App 里怎么用
App 只在三个时候探测:首次启动选完收听范围、下拉刷新、设置里点「检测可播放的源」。其他时候沿用上次的结果,不重复折腾。
探测是并发的,测通的台立刻出现在列表里,能马上听,不用等全部测完。进度条旁边有「停止检测」,按下去保留已经测到的,剩下的不测了。
测失败的台会从主页隐藏,但能在设置「电台管理 → 连不上的电台」里看到,长按就能换个地址。换完只存在本机,不用等我发版。