1. HLS与M3U8:直播点播背后的“切片播放”逻辑
先说个直接的结论:你看到的在线视频网站、直播平台、甚至手机里的监控回放,很大一部分都在走HLS协议,而你打开的播放列表文件,就是那个后缀叫“.m3u8”的东西。干这行这么多年,我每次面试新人都会问一个问题:“MP4那么成熟,为什么这些平台不直接播一个MP4文件拉倒?”能把这问题讲明白的人,基本就对流媒体入门了。
HLS全称HTTP Live Streaming,是苹果最早提出的一套流媒体传输协议。它的核心思路特别朴素:把一个完整视频切成许多个小的TS视频片段,再通过一个文本索引文件(也就是m3u8)告诉播放器“你先播哪几段、每段在什么地址、时长多少、有没有加密”。播放器拿到这个索引后,就像拿着菜单点菜一样,按顺序拉取切片并连续播放。这样你的播放器从头到尾只“认识”一个个小TS文件,根本不需要一次性加载整部电影。
这解决了两个大问题。第一,点播场景下的拖动播放。传统MP4虽然支持服务器端range request,但要在浏览器里做到精确秒拖,要么依赖moov元数据前置,要么就得多做一层转封装,坑不少。切了片之后,拖动播放就是“跳到某个时间点,找到对应的几个分片,从那里继续拉”,逻辑非常清晰,播放器实现起来也省心。第二,直播场景下的天然优势。直播流的本质是持续产生的数据,没法一开始就确定总时长。HLS直播可以不断生成新的切片、更新m3u8索引,播放器隔几秒刷新一次索引,就能像看“幻灯片连播”一样追直播。这就是HLS延时的来源——切片越长,延迟越大,后面我会专门聊怎么取舍。
说句实在话,HLS并不是性能上最极致的方案。论低延迟它有WebRTC和LL-HLS在挑战,论兼容性它也不如FLV在PC端那么“轻”。但它强在完全基于HTTP协议,CDN分发系统直接拿静态文件的方式就能扛住大规模并发,不需要服务器端维持复杂的长连接状态。这意味着只要你有静态文件托管能力,就能把一套HLS服务搭起来,成本低、扩展性好,这也是它能霸屏多年、直到现在依然被主流平台广泛采用的原因。
这篇东西适合谁看?如果你是刚接触流媒体开发的前端,或者是负责视频上线、维护点播系统的后端运维,再或者只是想把手里一个本地视频转成网页能播的格式,这里面的内容基本可以照着实操。我会把切片、加密、多码流自适应这三块核心拆开揉碎,附上我踩过的雷和实测过的命令,尽量让你少走弯路。
2. 视频切片:从录制流到m3u8索引的完整拆解
2.1 切片前必须搞懂的两个参数:切片时长和GOP对齐
切片是HLS的地基。切片做得不好,后面加密、自适应全是白搭。首先要理解的是切片时长,也就是每个TS片段大概多少秒。常规点播场景我会选4到10秒,直播场景为了控制延迟会切得更短,2到4秒比较常见。这个时长的选择直接影响两个指标:用户起播等待时间和拖动定位粒度。每个切片都是独立的解码单元,播放器至少要先下载一个完整的切片才能开始播放。如果你切成12秒一片,用户点开视频可能要等好几秒才能看到画面,而且拖动进度条时最小定位单元就是12秒,体验会显得“隔路”。
另一个概念是GOP对齐,这属于新手比较容易翻车的点。视频压缩时编码器会按照“关键帧(I帧) + 若干预测帧(P帧/B帧)”的规律生成一组组画面,这一组就叫做GOP。HLS切片的最佳实践是让每个切片都是从关键帧开始、到关键帧之前结束。为什么?因为非关键帧需要依赖前一个关键帧才能解码,如果切片起点不是I帧,播放器单独拉取这个切片时根本无法解码出完整画面,就会黑屏或花屏。ffmpeg切片时可以通过在编码参数里设置-force_key_frames "expr:gte(t,n_forced*切片时长)"来强制每个分片起点正好是I帧,也可以直接设置-sc_threshold 0关闭部分场景检测,避免编码器因为画面变化而额外插入关键帧导致切片不均匀。
还有一点容易被忽略:切片命名策略。生产环境我习惯用类似于/output/segment_%05d.ts的带五位数序号命名。不要用带时间戳但精度不高的命名方式,比如segment_1678901234.ts,因为直播切片一旦刷新,播放器需要从m3u8索引里精确知道每个文件的地址,如果文件名和索引对不上,就会产生404。更稳妥的做法是用整数序号,m3u8里通过EXTINF记录时长,通过EXT-X-MEDIA-SEQUENCE记录起始序号,这样既方便直播滚动切片,也方便点播做整段拼接。
2.2 用ffmpeg把一段MP4切成HLS流:一条命令搞定
说再多理论不如直接动手。把一段普通MP4转成带m3u8索引的TS切片,ffmpeg是最常用的工具。下面这条命令是我日常用的标准点播切片命令:
ffmpeg -i input.mp4 \ -codec copy \ -f hls \ -hls_time 6 \ -hls_playlist_type vod \ -hls_segment_filename "output/segment_%05d.ts" \ output/index.m3u8逐个选项解释一下。-codec copy表示不重新编码,直接把原始视频数据原样封装成TS格式,速度快,也不损失画质。但这里有个限制:如果原始视频是MP4容器里的H.264+AAC,而且moov元数据正常,那基本没问题;如果原始视频是其他编码比如HEVC,虽然也能封装,但要注意目标的浏览器和播放器是否支持。-hls_time 6指定目标切片时长为6秒,实际切出来是接近6秒但不一定精确等于6秒,因为必须卡在关键帧处断开。-hls_playlist_type vod表示这是一个完整的点播列表,播放器播完就结束了,不会继续请求刷新;直播场景则用event或者直接省略,加上-hls_list_size 0来保留所有切片。最后-hls_segment_filename控制切片文件名,后面的index.m3u8就是输出的索引文件。
直播场景的切片命令稍有不同,因为直播流是持续不断的,索引文件也要不断更新。假设输入是一个RTMP推流地址,转成HLS直播可以写成:
ffmpeg -i rtmp://your-server/live/stream \ -c:v libx264 -preset veryfast -tune zerolatency \ -c:a aac -b:a 128k \ -f hls \ -hls_time 4 \ -hls_list_size 6 \ -hls_flags delete_segments+append_list \ -hls_segment_filename "live/segment_%05d.ts" \ live/index.m3u8这里的-hls_list_size 6意思是索引里最多保留最近的6个切片,更早的切片会被清理掉或者从列表里移除,防止直播时间长了目录爆满。同时-hls_flags delete_segments+append_list会让ffmpeg自动删除已经过期的切片文件,并持续更新m3u8。用这组参数跑起来,配合任意一款支持HLS的播放器打开index.m3u8地址,基本就能看到低延迟直播效果。
2.3 切片时常见的三个坑:音画不同步、切片变长、拖动卡顿
片切不对,播放体验一路崩。第一个高发问题是音画不同步。通常发生在用-codec copy直接转封装时,因为TS切片要求音频和视频的起始时间戳对齐,而原始文件打包格式里音视频流的起始时间戳可能有偏差。解决思路有两种:一是重新编码音视频,ffmpeg会强制对齐时间戳;二是用-copyts和-muxdelay 0这类参数调整时间戳偏移,但效果取决于源文件规范性。我的建议是,如果源文件本身音画同步没问题,切片后不同步了,优先检查是否混合使用了不同帧率的视频源,或者转封装时是否加了-ss这种截取参数导致时间戳没重算。
第二个坑是切片时长“飘了”。你设置了-hls_time 6,结果切出来的文件有3秒的、有9秒的、有15秒的,特别不均匀。很大概率是源文件关键帧间隔不稳定。比如原本用手机录的视频,遇到画面剧烈变化时硬件编码器可能强制插入了I帧,导致切片点落在不同位置。排查方法是对原视频跑一下关键帧分析,或者直接放弃codec copy,改成重新编码并显式指定-force_key_frames。另外某些源文件本身GOP很大,关键帧间隔20秒,你再怎么设置-hls_time 6也切不出6秒的切片,因为切片必须卡在关键帧上,所以这时候要么接受实际切片时长,要么重新编码把GOP调小。
第三个坑是点播拖动进度条时卡顿几秒甚至花屏。这个通常是因为切片没从关键帧开始,或者切片与切片之间解码上下文没衔接好。更隐蔽的原因是切片时长太短且关键帧穿插过多,导致播放器需要频繁重新初始化解码器。我见过有人把切片时长设成1秒,结果每个切片都自带完整SPS/PPS,解码开销增大,低端设备反而卡。经验公式是点播场景4到8秒比较平衡,直播场景2到4秒平衡延迟和流畅度。切片不是越短越好,这点要牢记。
3. AES-128加密:保护你的HLS视频流
3.1 加密解决什么问题,HLS加密原理其实很简单
视频切片生成之后,就是一堆放在服务器上的TS文件,任何知道m3u8地址的人都能把它们下载下来拼成完整视频。如果你的内容是付费课程、VIP电影或内部培训视频,裸奔让人家随便扒,肯定说不过去。HLS协议自带一套标准加密方案:AES-128加密。
原理不复杂。首先你要生成一个16字节的密钥,这就是AES-128的Key。切片被发送给播放器之前,用这个Key对每个TS文件进行加密。播放器想要播放,必须先从m3u8索引的EXT-X-KEY标签里拿到密钥地址,再去下载密钥解出明文。除了解密后的TS片段,播放器拿不到任何完整的原始视频数据。哪怕有人把m3u8和TS全下载了,没有密钥也只能得到一堆加密后的乱码。
这里的关键在于,密钥怎么分发。很多人在密钥这里踩坑。密钥如果直接放在公网可访问的HTTP地址上,那懂行的人可以顺着EXT-X-KEY里的URI去把密钥也下载下来,加密就形同虚设。常规做法是做一个鉴权接口,返回Key之前先校验请求里带的token或cookie。播放器先请求一个有鉴权的Key URL,服务端确认你有权限后再返回Key内容。当然这样也谈不上绝对安全,因为播放器内部迟早会拿到明文Key,有毅力的人总能逆向抠出来。但对抗普通用户顺手扒视频,这个强度足够。
3.2 生成Key并加密切片:ffmpeg实操
生成密钥本身非常简单,一条命令即可:
openssl rand 16 > enc.key这会生成一个16字节的随机密钥文件。然后需要生成一个配套的IV,IV是可选的,但建议加。IV相当于初始偏移量,让每一段明文在相同数据下加密结果也不一样,增强安全性。生成IV的方式也可以直接用openssl随机生成16字节:
openssl rand 16 > enc.iv接下来进行加密切片。ffmpeg的hls muxer原生支持AES-128加密,只需要提供一个包含密钥信息的key info文件。这个文件每行三个参数:密钥URI、密钥文件路径、IV(十六进制格式)。比如key_info.txt内容如下:
enc.key enc.key 0123456789abcdef0123456789abcdef第一行是写入m3u8里的密钥URI地址;第二行是本地密钥文件路径供ffmpeg读取;第三行是IV,写成十六进制字符串,长度32个十六进制字符(也就是16字节)。然后执行:
ffmpeg -i input.mp4 \ -c:v libx264 -c:a aac \ -hls_time 6 \ -hls_playlist_type vod \ -hls_key_info_file key_info.txt \ -hls_segment_filename "enc/segment_%05d.ts" \ enc/index.m3u8输出后的m3u8里面会多出类似这样的一行:
#EXT-X-KEY:METHOD=AES-128,URI="enc.key",IV=0x0123456789abcdef0123456789abcdef播放器读到这行就知道后续的TS片段是AES加密的,会先请求enc.key这个地址拿密钥,再按指定IV解每个切片。注意这里的URI是相对路径,如果你的播放页面不在同一域名下,要保证相对路径能够正确解析到密钥文件,或者直接写成绝对URL。
3.3 播放器端解密逻辑与密钥管理技巧
很多前端新手在遇到HLS加密时会问:浏览器里<video>标签直接播放加密m3u8能行吗?原生<video>标签是不行的。你需要一个支持MSE的播放器,比如hls.js。hls.js完全实现了HLS解密逻辑,会在内部请求密钥并解TS片段,然后喂给Media Source Extensions。所以在支持MSE的浏览器环境中,使用hls.js播放加密流是透明的,前端基本不需要额外写解密代码。
但密钥地址的鉴权处理是有技巧的。生产环境中我会给EXT-X-KEY的URI配置成一个带签名参数的接口,比如/getkey?id=xxx&token=xxx&expires=xxx。播放器请求m3u8时,服务端动态把这个URI注入进去。这样即使m3u8被外部拿到,请求密钥的地址已经带了token,有效期也有限,服务器可以拒绝过期或无权限的请求。不建议把所有用户共用一个长期有效的静态密钥地址,那样等于把门钥匙挂在门边上。
另外要注意密钥文件必须和m3u8、TS切片保持同源策略,否则CORS会拦截。用hls.js播放时,密钥请求会遵循跨域规则。你需要在密钥接口的响应头里加上Access-Control-Allow-Origin,允许播放器所在域名的跨域访问,不然密钥拉不下来,播放器就报错。这是一个特别常见的坑,很多人在本地测试没问题,一上CDN就黑屏,多半是跨域配置漏了。
3.4 关于“AES什么模式每次加密结果都不一样”的经验谈
有朋友在群里问过:“我用AES加密同一个文件,每次加密结果都不一样,正常吗?”这得看加密模式。ECB模式下同一段明文加密结果相同,而CBC模式就算明文一样,只要IV变了,密文就不同。HLS的AES-128默认使用的是CBC模式,并且每个TS切片通常会指定新的IV。ffmpeg处理时会自动更新IV,或者在m3u8里通过IV=参数显式指定。如果你把IV固定住,同一个切片重复加密结果就是一致的。所以“每次结果不一样”在CBC模式下是正常现象,不必恐慌,反而是ECB模式泄露明文模式特征,安全性弱,不建议用在视频加密场景。
实操中还有个小细节:某些老播放器对IV的支持有问题,建议在EXT-X-KEY中同时写清楚IV=0x...,但也要确保IV和实际加密使用的IV对齐。如果IV写错了,播放器解出来就是花屏。调试时可以先不加密跑一遍,再加密跑一遍,如果加密后花屏,优先检查IV和密钥长度。密钥必须是16字节,IV必须也是16字节,多一个少一个都解不对。
4. 多码流自适应:让视频在不同网速下流畅播放
4.1 Master Playlist与Variant Streams:一份总索引,多份子索引
前面我们生成的m3u8是单一码率的播放列表。真实场景下,用户网络环境千差万别,有人开着5G看4K,有人在地铁里只剩一格信号。为了让大家都能“流畅地看”,视频平台通常准备多个清晰度版本:1080P、720P、480P、360P。播放器根据实时网络带宽自动选择最合适的那个版本,切换过程对用户基本无感。
这就是多码流自适应。HLS通过嵌套索引来实现。最外层一个“Master Playlist”文件,里面不直接写TS文件,而是写多个子m3u8的地址,每个子m3u8对应一个码率/分辨率的版本。播放器先加载Master Playlist,解析出所有备选码率,然后根据带宽估算逻辑动态选择某一个子播放列表去拉取切片。当网络变差时,播放器会从高清子播放列表切到标清子播放列表,避免卡顿。
Master Playlist长这样:
#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1920x1080,FRAME-RATE=25.000 1080p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=1280x720,FRAME-RATE=25.000 720p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360,FRAME-RATE=25.000 360p/index.m3u8BANDWIDTH是预估带宽,单位是bps,它是个综合值,包含视频码率加音频码率再加一些协议开销。播放器会用这个值作为初选依据。RESOLUTION是分辨率,给播放器参考,某些播放器还会根据屏幕大小做限制。
4.2 如何生成多码流版本:转码参数的选择
生成多码流版本的思路非常直接:把同一份原始视频转成多个不同码率和分辨率的副本,然后分别切片。ffmpeg单条命令可以一次完成多个目标的转码,但实际操作我更推荐分开执行,方便排查哪个版本出了问题。下面以生成720p和360p为例:
ffmpeg -i input.mp4 \ -vf scale=1280:720 \ -c:v libx264 -b:v 2500k -maxrate 3000k -bufsize 5000k \ -c:a aac -b:a 128k \ -f hls -hls_time 6 -hls_playlist_type vod \ -hls_segment_filename "720p/segment_%05d.ts" \ 720p/index.m3u8 ffmpeg -i input.mp4 \ -vf scale=640:360 \ -c:v libx264 -b:v 900k -maxrate 1100k -bufsize 1800k \ -c:a aac -b:a 96k \ -f hls -hls_time 6 -hls_playlist_type vod \ -hls_segment_filename "360p/segment_%05d.ts" \ 360p/index.m3u8这里的参数核心是-b:v目标码率、-maxrate峰值码率、-bufsize缓冲大小。之所以要设置maxrate和bufsize,是为了防止画面剧烈变化时码率突然飙到很高,导致真实网络带宽跟不上。HLS自适应切换机制一般是在切片边界处切换,如果一个切片码率过高,播放器可能拉取耗时过长,切换效率就低了。把码率控制住,切片大小更均匀,自适应切换才更平滑。
另外分辨率比例要统一。如果你的源视频是横版16:9,缩放时尽量保持比例,用scale=1280:720这种固定宽高比不会变形。如果你要适配竖屏短视频,可能需要改变分辨率策略,但注意不要为了兼容低端机把分辨率拉得太低,文字会糊到看不清。
4.3 ABR算法:播放器如何决定切到哪个码率
多码流板块里最“玄学”的部分是自适应切换算法。不同播放器实现不同,但大致思路都是基于带宽估计。hls.js的ABR会根据最近下载几个分片的平均下载速度除以分片时长,估算出当前可用带宽,再结合Master Playlist里的BANDWIDTH参数确定该选哪个档次。传统算法还会附加一个安全系数,比如估出的带宽是1.5Mbps,而相邻两个码率是1.4Mbps和2.5Mbps,它会保守选择1.4Mbps,防止因为网络抖动导致下载速度低于播放速度。
自适应切换最容易出现的问题是“乒乓效应”:网络波动时,播放器一会儿切到高清,一会儿切到标清,反复横跳,体验非常差。好的ABR算法会有“滞回区间”,也就是切换到高码率需要的带宽门槛高于停留在当前码率所需的带宽门槛,避免小幅波动就触发切换。还有一个细节是第一帧起播时不要盲目选最高码率,很多播放器会先选最低或适中码率快速出画面,之后再逐步提升到合适档位。这也是为什么你打开一个视频时初始画质通常偏模糊,等一两秒后变清晰的原因。
作为内容提供方,我们能做的是把码率梯次设计得合理一些,避免相邻档位差距过大,否则切换感知太明显。常规设计是每档码率乘以1.5到2倍递增,例如360P 800kbps、480P 1500kbps、720P 2500kbps、1080P 4000kbps。这样播放器切换时带宽需求变化不至于太跳跃,用户体验更顺滑。
4.4 多码流方案部署时容易忽略的三个点
第一,切片对齐。不同码率版本的切片时长和切片边界必须保持一致。比如约6秒一切,那所有版本都在源视频的第6、12、18秒处断开,而不能是720p切在6秒,360p切在5.5秒。如果边界不对齐,播放器在切换码率时可能遇到时间线错乱,出现跳帧或重复播放。实现方式是在每个版本的转码命令中都使用相同的-force_key_frames表达式,确保关键帧位置一致。
第二,Master Playlist和子Playlist之间的相对路径。如果你把Master放在/hls/index.m3u8,而各个子播放列表在/hls/720p/index.m3u8,那么Master里的引用要写成720p/index.m3u8。很多人直接写死绝对路径,一旦换域名或迁移CDN,路径就全废了。用相对路径配合CDN的目录结构,迁移成本会小很多。
第三,CDN缓存规则。HLS的m3u8索引是动态变化的(直播尤其明显),TS切片则相对静态。CDN上最好对*.m3u8设置较短的缓存时间(比如10秒),对*.ts设置较长的缓存时间(比如24小时)。如果缓存规则配置反了,直播索引被CDN缓存住,用户播放器刷新的索引永远都是几分钟前的,延迟会越来越大。点播场景虽然m3u8不会变,但为了后续能覆盖更新,也不建议把m3u8缓存死。
5. 常见问题与排查技巧实录
5.1 播放黑屏/无法加载m3u8
播放不了先别怀疑人生,按顺序排查。第一步,用浏览器直接打开m3u8地址看能不能下载,能下载就说明服务端文件在。第二步,检查m3u8的格式,重点看#EXTM3U头是否在第一行,每行结束是不是换行符(LF),文件结尾有没有额外空行或BOM。Windows下用记事本改过的m3u8经常带UTF-8 BOM,播放器会解析失败。第三步,如果是相对路径,确认相对于m3u8的目录位置是否正确;如果TS文件不在同一个目录下,要写清楚路径。第四步,看是否跨域,hls.js环境下要确保服务端配置了正确的Access-Control-Allow-Origin。
我遇到过最隐蔽的一种情况:服务器把.ts文件当成下载附件而不是媒体流来响应,导致播放器拉取不到正确的数据。解决办法是配置正确的MIME类型,.m3u8对应application/vnd.apple.mpegurl或application/x-mpegURL,.ts对应video/mp2t。部署在Nginx时可以用types指令声明,Apache则用AddType。
5.2 加密后播放黑屏或花屏
如果同一个视频不加密能播,加了加密就黑屏或花屏,方向性很强。先把密钥下载下来用openssl验证一下长度是不是16字节:openssl enc -aes-128-cbc -d -in encrypted.ts -out test.ts -K 密钥十六进制 -iv IV十六进制,手动解密一个切片看能不能正常播放。能正常,就说明ffmpeg生成过程OK,问题出在播放器拉密钥环节;不能正常,说明加密参数有问题。最常见的坑是密钥URI的路径不对,或者IV没写、写错。另外有些CDN会自动改写m3u8里的URI,把enc.key改成一个不可访问的路径,需要检查CDN的改写规则是否干扰了密钥地址。
还有一点:如果你的TS切片在加密前已经被某些缓存代理缓存过,播放器可能拿到的是旧数据和解密完全不匹配。清缓存再测一遍,基本能排除。
5.3 直播延迟太高,怎么压都压不下去
HLS直播的最大痛点就是延迟。如果切片时长是10秒,那理论最低延迟也在10秒以上,实际加上网络传输、播放器缓冲,延迟轻松到30秒以上。想降低延迟,第一件事是把切片时长压到2到4秒。第二件事是播放器端要配置低延迟模式,比如hls.js里的lowLatencyMode: true。第三件事是开通LL-HLS,这是HLS的进化版,基于HTTP/2和分块编码,延迟可以做到1到3秒。但LL-HLS要求服务端、CDN和播放器都支持,部署门槛较高。如果你的用户没那么苛刻,4秒切片配合低延迟模式,延迟可以控制在5到8秒,大多数人已经能接受。
压延迟的同时要注意码率稳定性。切片变短,每个切片的独立性和文件数量就会增加,网络请求次数变多,如果播放器并发拉取能力有限,反而可能频繁等待。实际优化时我会在延迟和切片大小之间取一个平衡,直播场景优先保证流畅,延迟其次,毕竟没人愿意看一分钟卡三次的视频。
5.4 几个热词背后的真实场景:下载限制、转换失败、vue播放
最近总看到有人搜“hls下载只能在前次下载120分钟后进行”“m3u8视频转换失败”“vue播放m3u8”之类的问题。有些是平台做了策略,不展开。但“m3u8视频转换失败”我可以说说思路:很多人下载m3u8后用ffmpeg直接转MP4,命令报错多半是因为切片没下全,或者网络中断导致某些TS文件损坏。用ffmpeg转的话,先保证所有切片都在本地并按顺序重命名,再用ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4,其中filelist.txt每行写file 'segment_00000.ts'。如果个别TS文件坏掉,可以先用ffprobe看下时长断点,把坏掉的那一段重新下载。
至于Vue项目里播放m3u8,常规做法是安装hls.js,然后在mounted里初始化:
import Hls from 'hls.js'; const video = this.$refs.video; if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource('https://example.com/index.m3u8'); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play(); }); }如果你用的是video.js,配合videojs-contrib-hls也能播。低版本浏览器和iOS Safari原生支持HLS,直接给<video src="xxx.m3u8">就行,但Android Chrome原生不支持,所以还是需要hls.js兜底。这个场景很典型,本质是前端播放器能力问题,不是流本身有问题。
再补充一个热词“菠萝.m3u8”之类,其实就是某些站点给视频起的名。不在乎内容,只提醒一句:从不可信来源获取的m3u8和TS文件,不要直接丢进内部系统解析,要警惕文件名拼接和路径穿越等问题。如果你的服务端生成m3u8的时候拼接了用户输入的文件名,记得做合法性校验,防止被构造出意外路径。这属于流媒体服务的安全生产底线,务必重视。
写在最后
做HLS这块内容也这么多年了,前后踩过的坑写出来估计还能再凑一篇。切片、加密、多码流自适应,这三件事单独拎出来都不难,难的是把它们串成一个稳定运行的线上系统。我自己的体会是:调试时不要用大文件,先用一分钟的小片段跑通全流程;遇到播放异常,第一时间先抓取m3u8文件和网络请求,看是索引错了、切片404了,还是密钥没拿到;把日志和请求证据拿到手再动代码,效率会高很多。
如果这篇文章能帮你少走几个弯路,那我觉得这套经验分享就值了。后面我打算再整理一份HLS线上告警项和指标监控清单,比如切片生成延迟、分片大小波动、CDN回源失败率这些,如果你也在做流媒体服务,可以多留意这一块。