Jasna 流式播放(Streaming)实战指南:边修复边看,HLS 浏览器播放与 Stash 集成
Jasna 流式播放(Streaming)实战指南:边修复边看,HLS 浏览器播放与 Stash 集成
Jasna 的 Streaming 模式让视频修复从"先跑完再看"变成"边修复边看":程序仍在后台对视频做画面修复(去码、超分等),你就能在浏览器里直接播放已经修复好的部分,并且支持任意拖拽跳转(Seeking)。本文以仓库中的 docs/en/streaming.md 为核心脉络,结合 jasna/streaming.py、jasna/streaming_pipeline.py、jasna/streaming_encoder.py 等源码与 tests/test_streaming.py 测试用例,完整讲解浏览器播放器的启动方式、全部 CLI 参数、底层 HLS 分片与按需生产机制,以及 Stash 场景下的集成步骤与排错方法。读完本文,你可以立即用一条命令开启实时修复预览,也能在自建 Stash 媒体库中直接调用 Jasna 的修复能力。
流式播放能做什么
传统修复流程是"输入视频 → 等待整个文件处理完毕 → 得到输出文件"。Jasna 的 Streaming 模式把这个过程变成了"边处理边交付":
- 不需要等待整个文件:第一个分片(segment)处理完成后即可开始播放,后续内容在后台持续生产;
- Seeking 可用:拖拽进度条到任意位置后,管线会自动中断当前生产任务,从目标位置重新开始一轮修复;
- 不产生输出文件:处理结果通过 HTTP 以 HLS(HTTP Live Streaming)形式直接推送给浏览器,磁盘上只保留临时分片。
从 jasna/main.py 的 _run_streaming 可以看出,流式模式与普通输出模式完全并行:先启动 HLS 服务器,再进入"等待选片 → 运行修复管线 → 卸载视频"的循环。
浏览器播放器:一条命令开始
在命令行启动流式模式,Jasna 会自动打开内置的浏览器播放器页面:
jasna --stream
在 Windows 上使用与 GUI 相同的可执行文件:
jasna.exe --stream
启动后终端会打印两行信息(见 jasna/main.py):
HLS stream: http://localhost:8765/stream.m3u8
Browser: http://localhost:8765/
浏览器页面会呈现一个极简播放器:在输入框中粘贴视频文件路径(或点击 Browse 使用系统文件对话框选择),回车即可开始播放。页面逻辑内置于 jasna/streaming.py 的 _PAGE_HTML 中,通过 hls.js 拉取 /stream.m3u8 播放列表并逐段加载 seg_NNNNN.ts 分片,右上角 Change Video 按钮可随时切换视频(对应 DELETE /open 请求)。
常用选项
| 选项 | 默认值 | 说明 |
|---|---|---|
--stream |
关 | 启用 HLS 流式模式,不写输出文件(见 jasna/main.py) |
--stream-port |
8765 |
HLS HTTP 服务端口(jasna/main.py) |
--stream-segment-duration |
4.0 |
每个 HLS 分片的时长(秒),即管线每处理多少秒视频交付一个分片(jasna/main.py) |
--no-browser |
关 | 不自动打开浏览器,适合远程服务器或自行打开播放器的情况 |
如果你在远程主机或只想自己控制浏览器,可用:
jasna --stream --no-browser --stream-port 9000
然后手动访问 http://<主机>:9000/。
两种启动方式
- 带输入启动:
jasna --stream --input video.mp4,服务器启动后直接开始处理该视频; - 无输入启动:
jasna --stream,服务器先就绪,播放器页面选好文件后(POST /open)才启动修复管线——这正是 Stash 集成所依赖的交互模式。
使用限制
--stream 与以下选项互斥,传入会直接报错(见 jasna/main.py):
--segments:分片/切片导出不能与流式组合;--fmp4:分片 MP4 输出不适用于流式;- 文件夹输入与图片输入均不支持流式;
- LTX 修复模型不支持流式(
--restoration-model-name ltx ... does not support --stream); --retarget-high-fps同样被拒绝。
也就是说,流式模式固定使用 BasicVSR++ 修复模型与 H.264 编码,这一点在 docs/en/cli.md 帮助文本中也有明确注释("HLS streaming always uses H.264"),GUI 内置的 Video Player 同样遵循此规则(见 docs/en/gui.md)。
底层原理:按需生产的 HLS 服务器
浏览器播放器只是表象,真正支撑"边修复边看"的是仓库中的三层实现:HLS 服务器(jasna/streaming.py)、流式管线循环(jasna/streaming_pipeline.py)与流式编码器(jasna/streaming_encoder.py)。
HTTP 接口一览
HlsStreamingServer(jasna/streaming.py)基于 ThreadingMixIn + HTTPServer 实现,绑定 0.0.0.0,主要路由如下:
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | / |
返回播放器 HTML 页面 |
| GET | /status |
查询当前是否正在流式播放及视频路径 |
| GET | /stream.m3u8 |
返回 VOD 型 HLS 播放列表,支持 ?start=秒数 从指定位置生成 |
| GET | /seg_NNNNN.ts |
返回指定编号的视频分片(video/mp2t),带 ?epoch= 防过期播放 |
| POST | /api/browse |
弹出系统文件选择对话框,返回所选路径 |
| POST | /open |
选择视频文件开始流式播放(可带 start 指定起始时间) |
| DELETE | /open |
停止当前流,回到选片界面 |
所有响应都带 CORS 头(Access-Control-Allow-Origin: *),支持跨域代理访问。
播放列表与"防串台"机制
视频元数据加载后,服务器会调用 _generate_vod_playlist(jasna/streaming.py)一次性生成整段视频的 VOD 播放列表:按 segment_duration 把总时长切成 ceil(total / segment) 个分片,最后一个分片取余长,写入标准的 #EXTM3U / #EXT-X-VERSION:3 / #EXT-X-PLAYLIST-TYPE:VOD / #EXT-X-MEDIA-SEQUENCE / #EXTINF / #EXT-X-ENDLIST 结构。
这里有一个关键的工程细节——epoch 机制:每次发生 Seek 或切换视频时 playback_epoch 递增,播放列表里每个分片地址都会带上 ?epoch=N。当播放器还拿着旧时代的播放列表请求旧分片时,服务器校验 epoch 不匹配直接返回 404,从而避免"旧播放器请求重新触发新一轮 Seek 生产"的串台竞态。测试 tests/test_streaming.py(test_old_playback_cannot_restart_new_seek)正是验证这一行为。
分片按需生产与流量控制
修复管线不会无脑把整部视频处理完,而是以播放器的消费节奏为准:
- 播放器每请求一个分片,服务器都会通过
notify_segment_requested记录当前_highest_requested_segment(玩家已请求的最高分片号),并用条件变量唤醒管线; - 管线在写入分片时调用
wait_for_demand(current_segment, max_ahead, ...)(jasna/streaming.py),若已生产分片超过"播放器已请求分片 + 3"(_MAX_SEGMENTS_AHEAD = 3,见 jasna/streaming_pipeline.py),就暂停生产,避免无谓的 GPU 空转与磁盘膨胀; - 同时用
_evict_old_segments清理播放器身后过旧的分片(保留2 * max_ahead = 6个,jasna/streaming.py),并记录_evicted_through,被驱逐的分片若再被请求会触发重新生产。
tests/test_streaming.py 的 TestDemandFlowControl 用例系统验证了这套机制:生产超前会阻塞、播放器追上来会唤醒、cancel 事件能解除阻塞(tests/test_streaming.py)。
Seek:中断当前 pass,从目标位置重启
Seek 由 jasna/streaming_pipeline.py 的 _streaming_loop 处理:
- 播放器请求某个未生产/已驱逐的分片时,服务器判定
needs_seek(向后退始终需要 Seek;向前跳超过_FORWARD_SEEK_THRESHOLD = 5个分片也需要,见 jasna/streaming.py 与 jasna/streaming.py); - 服务器通过
request_seek设置seek_target_segment,当前正在跑的修复 pass 通过poll回调检测到后立即取消(_run_streaming_pass中的poll,jasna/streaming_pipeline.py); - 流式编码器执行
flush_and_restart(start_number),丢弃未写完的分片、杀掉旧 ffmpeg、从新起点重启一个编码进程; - 新 pass 从目标分片开始生产,期间带
-ss跳转输入与-output_ts_offset保持时间戳连续(见 jasna/streaming_encoder.py)。
当整个视频都生产完毕(没有 Seek 请求)时,管线标记 mark_finished 并进入"空转等待"状态,直到用户发起新的 Seek 或切换视频(jasna/streaming_pipeline.py)。
编码器:GPU 硬件 H.264 + 音频直拷
StreamingEncoder(jasna/streaming_encoder.py)把修复管线输出的 RGB 帧通过管道喂给 ffmpeg 编码成 HLS MPEGTS 分片,按 GPU 厂商选择不同编码器:
- NVIDIA:
h264_nvenc,preset p4、tune ll(低延迟)、rc vbr、cq 19、bf 0(禁用 B 帧)、highprofile、开启spatial-aq/temporal-aq、rc-lookahead 8,并通过-gpu指定设备序号(多卡时取device.index); - AMD:
h264_amf,usage lowlatency_high_quality、quality balanced、rc qvbr、qvbr_quality_level 19、bf 0、highprofile。
其他要点:
- GOP 大小 =
round(fps × segment_duration),保证每个分片自带关键帧、可独立解码; - 若源文件存在,会以
-map 1:v:0 -map 0:a:0?复用音轨(-c:a copy直拷),因此浏览器里能看到原声; - 非方形像素源会自动附加
setsar=宽/高滤镜; - ffmpeg 必须可用(打包工具目录或 PATH 中),否则直接抛错提示;测试 tests/test_streaming.py 对 nvenc 参数、
start_number、-ss、-output_ts_offset、音频直拷等命令构造均有断言。
在 Stash 中集成:场景即修即播
除了内置浏览器播放器,Jasna 还能以"外部修复引擎"的方式嵌入 Stash(开源媒体管理/播放器)流程:当你播放某个 scene 时,Stash 启动 Jasna 并实时提供修复后的画面。这需要配合一个定制版 Stash(Stash v0.30.1-jasna)使用。
集成步骤
- 下载定制版 Stash:获取
Stash v0.30.1-jasna版本并解压; - 设置环境变量:在启动 Stash 之前设置以下两个变量:
JASNA_CLI_PATH:jasna.exe的完整路径;JASNA_WORKING_DIR:jasna.exe所在目录的完整路径;
- 先做一次"预热"运行(重要):用你将在 Stash 中使用的相同设置,在命令行对一段短视频手动执行一次流式播放。第一次运行会为你的 GPU 准备引擎/编译文件,耗时较长;如果跳过这一步,Stash 会在等待 Jasna 就绪时超时放弃;
- 启动 Stash 并播放场景:此后 Stash 会在播放时自动拉起 Jasna,视频被实时修复后再播放。
常见报错与排查
如果 Stash 日志出现:
timeout waiting for jasna-cli to become healthy
按以下顺序排查:
- 先检查
JASNA_CLI_PATH:确认路径完整、指向真实的jasna.exe,且与JASNA_WORKING_DIR一致; - 再做步骤 3 的预热运行:确保 GPU 引擎已就绪,避免首次运行耗时过长导致 Stash 判定超时。
与 GUI 内置 Video Player 的取舍
如果不依赖 Stash,也可以在 Jasna 图形界面中直接使用 Video Player(docs/en/gui.md):在窗口顶部点击 Video Player,选择检测模型、检测阈值及可选的次级修复(RTX Super Res 或 UNet 4x),选好视频后即可边修复边播放,同样不产生输出文件。GUI 播放器支持空格暂停/播放、左右方向键跳转 30 秒,并实时显示"已提前修复的秒数"。
两种方式的共同约束与差异:
- 都固定使用 BasicVSR++,不使用 LTX;
- CLI
--stream面向浏览器/HLS 与 Stash 集成,播放体验由 hls.js 驱动,适合远程/无人值守场景; - GUI Video Player 在应用内播放,且要求队列与 Restore preview 未在运行(见 docs/en/gui.md)。
质量保障:测试如何守护流式链路
流式功能并非"黑盒魔法",仓库用 tests/test_streaming.py(约 1479 行)从多个层面做了覆盖,可作为理解与验证的入口:
- 播放列表生成:
TestGenerateVodPlaylist验证分片数量上取整、末分片时长、#EXT-X-ENDLIST等结构; - 服务器状态机:
TestHlsStreamingServer验证视频加载、分片计数、Seek 请求/消费、多 Seek 后者胜出、视频切换触发事件等; - 流量控制:
TestDemandFlowControl验证超前暂停、玩家追赶快进、取消解阻塞、旧分片驱逐; - 编码器生命周期:
TestStreamingEncoder*用 mock 的 ffmpeg 进程断言命令构造(nvenc、SAR、start_number、音频直拷、-ss与-output_ts_offset),并覆盖 BrokenPipe、写线程竞态、超时强杀等异常路径; - HTTP 行为:
TestHttp*验证 CORS、manifest 裁剪(?start=40从第 10 个分片开始)、过期 epoch 返回 404、分片等待播放列表定稿后才可读取等。
这些测试同时证明了流式模式对播放器节流、Seek 竞态、编码异常等场景都有明确处理策略,可作为二次开发或深入阅读的起点。
适用前提与小结
流式模式的核心前提是:GPU 修复速度接近或快于实时播放速度。如果播放器状态栏(GUI)或分片请求长期赶不上生产,说明硬件处理能力不足以支撑实时预览,此时应降低分辨率、减少修复负担或改用离线导出。另外,流式模式要求 ffmpeg 可用、首次运行需完成 GPU 引擎编译(这也是 Stash 场景必须"预热"的原因)。
一句话总结:jasna --stream 把"修复"和"观看"从串行变成并行,借助 HLS 分片、按需生产与 Seek 中断重启机制,实现了真正可用的边修复边看体验;而 Stash 集成则把这一能力延伸到媒体库播放流程中。相关实现与测试可分别查看 jasna/streaming.py、jasna/streaming_pipeline.py、jasna/streaming_encoder.py 与 tests/test_streaming.py。