一个专为低配设备优化的 Bilibili 直播录制后端。
- ✅ 手动触发录制任务,实时录制直播流
- ✅ 支持多格式录制(HTTP-FLV / HLS-TS / HLS-fMP4)
- ✅ 自动录制 - 为直播间配置自动开播录制
- ✅ 可选录制时长 - 手动开始录制时可指定时长上限,时间到后自动停止;也可在订阅配置中预设自动录制的时长上限;支持设为无限录制
- ✅ 直播通知 - 实时推送开播通知(网页/手机推送)
- ✅ 自动分段轮转 - 当直播过程中发生直播 PK 等分辨率变更时,自动切换到新的录制分段文件,避免文件花屏或损坏
- ✅ 支持多个直播间同时录制
- ✅ 自动修复flv流,处理流中断和恢复(默认自动选择可用流格式,也支持手动指定格式)
- ✅ 自动转换到MP4 - 支援透过cloudconvert或本地ffmpeg
- ✅ RESTful API 管理录制任务或使用 Web 界面
- ✅ Android 嵌入支持 - 提供
cmd/androidlib的 c-shared 动态库输出,可将后端能力集成到 Android App - ✅ FRP 内网穿透 - 把录制后端安全暴露到外网,随时随地用 Web 界面 管理录制任务、文件和通知
- ✅ 文件管理和下载功能
- ✅ 在线播放 - 在 App 中直接预览和播放已录制的视频(仅限MP4)
- ✅ 支持匿名登录或账号登录
- ✅ 自动刷新 Cookie 保持登录状态
- ✅ 低内存与低 CPU 占用,适合在资源受限设备(如树莓派)上运行
- ✅ 默认配置下对 microSD 卡友好,能显着减低写入磨损 (针对树莓派)
服务器为 树莓派5B 16GB · 同时录制多个直播间(默认配置)
| 并发路数 | CPU峰值占用 | 内存占用 |
|---|---|---|
| 初始闲置 | ~0.0% | ~7MB |
| 1路并发 | ~0.8% | ~42MB |
| 2路并发 | ~2.6% | ~76MB |
| 3路并发 | ~3.0% | ~96MB |
| 4路并发 | ~3.4% | ~129MB |
| 5路并发 | ~4.1% | ~142MB |
| 恢复闲置 | ~0.0% | ~54MB |
Note
本程序使用了大量内存池,因此先前使用的一部分内存会回到内存池中等待日后重用,以减轻GC压力。
此外,本程序默认使用针对microSD卡优化的配置,因此内存占用会稍高以减少写入磨损。 你可以透过调控配置进一步降低内存占用。
可以从 GitHub Releases 页面下载预编译的二进制文件,选择适合你系统的版本:
bilirec-linux-amd64:适用于 x86_64 架构的 Linux 系统bilirec-linux-arm64:适用于 ARM64 架构的 Linux 系统bilirec-windows.exe:适用于 Windows 系统libbilirec-android-arm64-v8a.so/libbilirec-android-x86_64.so:适用于 Android(由cmd/androidlib以 c-shared 方式编译)
启动服务:
# 如果你下载了 amd64 版本
./bilirec-linux-amd64
# 或者如果你下载了 arm64 版本
./bilirec-linux-arm64如果你是 Windows 用户,直接双击 bilirec-windows.exe 启动服务。
可以通过构建镜像或直接运行容器来启动 Bilirec。
从源码构建镜像并运行(示例):
# 在仓库根目录构建镜像
docker build -t bilirec:latest .
# 运行容器(示例)
docker run -d \
--name bilirec \
-p 8080:8080 \
-e BILIBILI_LOGIN_MODE=controller \
-e PORT=8080 \
-e FRONTEND_URL=http://localhost:8080 \
-v /path/to/records:/app/records \
-v /path/to/secrets:/app/secrets \
-v /path/to/database:/app/database \
# 可选:启用 CloudConvert(替换为你的 API key)
-e CLOUDCONVERT_API_KEY=your_api_key \
bilirec:latest你也可以直接从 Docker Hub 拉取并运行镜像:
docker pull eric1008818/bilirec:latest # 最新测试版本请用 :edge
docker run -d \
--name bilirec \
-p 8080:8080 \
-e BILIBILI_LOGIN_MODE=controller \
-e PORT=8080 \
-e FRONTEND_URL=http://localhost:8080 \
-v /path/to/records:/app/records \
-v /path/to/secrets:/app/secrets \
-v /path/to/database:/app/database \
# 可选:启用 CloudConvert(替换为你的 API key)
-e CLOUDCONVERT_API_KEY=your_api_key \
eric1008818/bilirec:latestBilirec 已内置 Android 嵌入入口(cmd/androidlib),可将后端以 .so 方式集成到 Android App。
推荐直接参考官方 Android 客户端项目进行集成与使用:
构建示例(需安装 Android NDK):
make android输出目录:dist/android/<abi>/libbilirec.so(默认构建 arm64-v8a 与 x86_64)。
Android 库导出两个方法:
Start(configJson):启动服务(传入 JSON 配置)Stop():停止服务
Start 支持字段:
{
"basePath": "/data/user/0/your.app/files",
"port": 8080,
"host": "127.0.0.1",
"frontendUrl": "https://app.bilirec.org",
"outputDir": "/data/user/0/your.app/files/records",
"username": "admin",
"password": "changeme"
}其中 basePath 必填,其余字段可省略并使用默认值。Android 模式下会强制启用以下行为(与服务器版区分):
BILIBILI_LOGIN_MODE=controllerFRP_ENABLED=falseSILENT_ACCESS_LOG=trueCONVERT_TO_MP4=false
并使用更保守的移动端 I/O / 内存参数(例如较小写入缓冲、较低磁盘空间阈值)以避免前台卡顿。
请先参阅上面的安装部分完成部署,再按下方配置设置环境变量后启动服务。
默认使用 controller 模式登录。服务启动后,请打开 Web 界面并点击“登入”,再到右上角点击头像按钮完成 Bilibili 扫码登录。
你也可以使用 startup 模式改为在终端显示二维码然后扫码登录,或者使用 anonymous 模式直接匿名登录(未登录可能无法录制 1080p 直播)。
-
设置你的
FRONTEND_URL为https://app.bilirec.org/ -
直接访问
https://app.bilirec.org/进入登入界面 -
根据你所设置的
USERNAME和PASSWORD进行登录(如果未设置则直接进入)
如果启用 FRP 内网穿透:
-
启动内网穿透后,日志中会显示 FRP 连接状态和公网访问地址(如
https://abc1234567.tunnel.bilirec.org) -
进入
https://app.bilirec.org/后,在登录界面的服务器地址栏位输入公网访问地址(如https://abc1234567.tunnel.bilirec.org) -
使用
USERNAME和PASSWORD登录(暴露到公网后请务必设置USERNAME和PASSWORD)
推荐使用官方 Android 客户端:
使用方式:
- 优先从
bilirec-mobile的 Releases 下载并安装 APK。 - 打开 App 后,先在界面中点击启动按钮;启动成功后会出现“打开录制程序”按钮。
- 点击“打开录制程序”后,会跳转到
https://app.bilirec.org/,如果已安装 PWA 则会优先打开 PWA。 - 进入页面后直接点击“登入”即可;Android 默认连接
http://localhost:8080,且默认不启用账号密码。 - 若你需要自行开发或定制,再按
bilirec-mobile文档选择源码构建或库集成;库模式可先执行make android生成libbilirec.so。
除 Web 界面与 Android App 外,Bilirec 也提供 REST API 供程序调用、脚本控制或自行集成,详见下方 REST API 章节。
所有配置通过环境变量设置:
| 环境变量 | 说明 | 默认值 |
|---|---|---|
BILIBILI_LOGIN_MODE |
登录模式:startup(启动时登录)/ controller(由 API 控制登录)/ anonymous(匿名登录) |
controller |
HOST |
API 服务绑定地址(空值时监听所有网卡) | (空字符串) |
PORT |
API 服务端口 | 8080 |
SERVER_CRT |
可选:HTTPS 证书文件路径;当与 SERVER_KEY 同时设置时,Fiber 默认启用 HTTPS |
(未设置) |
SERVER_KEY |
可选:HTTPS 私钥文件路径;当与 SERVER_CRT 同时设置时,Fiber 默认启用 HTTPS |
(未设置) |
TRUSTED_PROXIES |
受信任反向代理 IP/CIDR 列表(逗号分隔),可填写 Nginx/FRP 等代理 IP,用于安全信任 X-Forwarded-For;默认 161.33.159.26 为公共 FRP 服务 IP。 |
161.33.159.26 |
FRP_ENABLED |
是否启用 FRP 内网穿透 | false |
FRP_SERVER |
FRP 服务器地址(格式:host:port) |
tunnel.bilirec.org:7000 |
FRP_TOKEN |
FRP 认证 Token;如果你有自己的 FRP 服务就填写,没有就留空。 | (空字符串) |
FRP_BASE_DOMAIN |
FRP 公网基础域名(用于组装公开访问地址) | tunnel.bilirec.org |
FRP_HTTPS |
FRP 代理使用的协议(true=HTTPS,false=HTTP) |
false |
FRP_SCHEME_HTTPS |
公网 URL scheme(true=https,false=http) |
true |
MAX_CONCURRENT_RECORDINGS |
最大同时录制数 | 3 |
MAX_RECORDING_HOURS |
单次录制最长时间(小时) | 5 |
MAX_RECOVERY_ATTEMPTS |
单次录制的最大重连尝试次数 | 5 |
MAX_RETRY_MINUTES |
直播中断后判断是否仍在直播的最长容忍时间(分钟) | 10 |
OUTPUT_DIR |
录制文件保存目录 | records |
SECRET_DIR |
Cookie 和 Token 保存目录 | secrets |
CONVERT_TO_MP4 |
录制完成后是否将可转换源文件(如 FLV/TS)转为 MP4 | false |
DELETE_SOURCE_AFTER_CONVERT |
转换后是否删除原始源文件 | false |
NO_CONVERT_IF_INVALID |
当源文件缺少音视频流时是否停止转码;true 时会阻止转换并返回错误,false 时只记录警告并继续转换。若当前系统未安装 ffprobe,原始视频检查会自动放行,转换仍会继续执行。 |
false |
PUBLIC_BASE_URL |
后端公开基址(仅用于生成预签名 URL,需为完整 URL) | (空字符串) |
FRONTEND_URL |
前端 URL(用于 CORS) | http://localhost:8080 |
WEBPUSH_SUBSCRIBER |
Web Push VAPID 的 subject(建议使用 mailto:you@example.com) |
mailto:webpush@example.com |
NOTIFY_SSE_TOKEN |
SSE 通知订阅 token(用于 /notify/sse?token=...;为空则禁用 SSE) |
(空字符串) |
USERNAME |
可选:启用用户名/密码认证时的用户名 | (未设置) |
PASSWORD |
可选:启用用户名/密码认证时的密码 | (未设置) |
VIEWER_USERNAME |
可选:仅查看权限的访客用户名 | (未设置) |
VIEWER_PASSWORD |
可选:仅查看权限的访客密码 | (未设置) |
JWT_SECRET |
JWT 签名密钥 | bilirec_secret |
DEBUG |
启用调试模式(会开启 pprof 和临时 hex token) | false |
PRODUCTION_MODE |
启用生产模式(影响 cookie 与 CORS) | false |
SILENT_ACCESS_LOG |
启用静默访问日志(仅记录 4xx/5xx 响应) | false |
DATABASE_DIR |
本地数据库目录(bbolt,用于持久化转换任务等) | database |
CLOUDCONVERT_THRESHOLD |
使用 CloudConvert 的文件大小阈值(字节) | 1073741824 (1 GB) |
CLOUDCONVERT_API_KEY |
可选:CloudConvert API Key(为空则禁用 CloudConvert) | (未设置) |
CLOUDCONVERT_CHECK_INTERVAL_SECS |
CloudConvert 任务状态轮询间隔(秒) | 180 |
CLOUDCONVERT_MAX_CONCURRENT_DOWNLOADS |
CloudConvert 最大并发下载数 | 1 |
FFMPEG_CHECK_INTERVAL_SECS |
本地 FFmpeg 转换任务轮询间隔(秒) | 60 |
FFMPEG_MAX_CONCURRENT_TASKS |
本地 FFmpeg 最大并发转换任务数 | 1 |
FFMPEG_ALLOW_DURING_RECORDING |
是否允许在录制进行中时执行 FFmpeg 转换任务 | false |
FFMPEG_ALLOW_DURING_RECORDING_MAX_ACTIVE_RECORDINGS |
仅当活跃录制数 <= 此值时,才允许在录制中执行 FFmpeg;<1 表示不设门槛(仍可通过 FFMPEG_ALLOW_DURING_RECORDING 控制) |
1 |
UPLOAD_BUFFER_SIZE |
上传时或向外部服务(如 CloudConvert)传输文件使用的缓冲区大小(字节) | 5242880 (5 MB) |
DOWNLOAD_BUFFER_SIZE |
文件下载 / 导出时使用的缓冲区大小(字节) | 5242880 (5 MB) |
STREAM_WRITER_BUFFER_SIZE |
流写入器(写入文件)缓冲区大小(字节) | 1048576 (1 MB) |
LIVE_STREAM_WRITER_BUFFER_SIZE |
实时流写入缓冲区(用于直播录制或实时下载,字节);更大的值减少 flush 频率,降低 SD 卡磨损 | 8388608 (8 MB) |
LIVE_STREAM_WRITER_SYNC_PERIOD_SECS |
实时流写入器执行周期性 sync 的周期(秒);设为 0 禁用周期性 sync(仅在 Close 时 sync),大幅减少 SD 卡磨损 |
0 |
LIVE_STREAM_WRITER_FLUSH_PERIOD_SECS |
实时流写入器执行周期性 flush 的周期(秒);值越大 flush 频率越低,越有利于减少 SD 卡写入频次 |
10 |
LIVE_STREAM_WRITER_CHAN_BUFFER_SIZE |
实时流写入器通道缓冲区大小(数据块数);更大的值可容忍写入延迟突变,但会增加内存占用 | 64 |
LIVE_STREAM_WRITER_BYTES_POOL_SIZE |
实时流写入器内存池的单个缓冲区大小(字节);应与实际流 chunk 大小相匹配 | 524288 (512 KB) |
SKIP_SMALL_FLUSH |
启用 microSD 磨损保护:若录制总写入量低于缓冲区大小则跳过 flush,避免写入小块数据(特别是低比特率流) | true |
SEQUENTIAL_WRITE |
启用全局 flush 锁以序列化多路录制的写入操作;仅在多路并发录制写入同一物理磁盘时建议启用,可显著降低 I/O 峰值 | true |
MIN_DISK_SPACE_BYTES |
录制所需的最小磁盘空间(字节),低于此值将拒绝新录制任务 | 5368709120 (5 GB) |
启用 FRP (FRP_ENABLED=true) 时,配置 Bilirec 作为内网穿透客户端连接到 FRP 服务器。
如果你只开启 FRP_ENABLED=true,程序会默认使用官方免费的公共 FRP 服务;只有当你另外设置 FRP_SERVER、FRP_BASE_DOMAIN 或 FRP_TOKEN 时,才会切换成自定义 FRP 配置。
基础参数:
FRP_SERVER:FRP 服务器地址和端口(格式:host:port)FRP_TOKEN:FRP 服务的认证 Token;有自己的 FRP 服务时填写,没有就留空。FRP_BASE_DOMAIN:生成公网访问地址的基础域名TRUSTED_PROXIES:受信任代理列表(逗号分隔),可填写 Nginx/FRP 等代理 IP;只有来自这些代理的X-Forwarded-For才会被信任。默认值161.33.159.26为公共 FRP 服务 IP
协议配置(可选):
FRP_HTTPS:FRP 代理使用的协议false(默认):使用 HTTP 代理(适合内部自架、有前置 HTTPS 层的情况)true:使用 HTTPS 代理(适合公开 FRP 服务)- ❗配置约束:当
FRP_ENABLED=true且FRP_HTTPS=true时,必须同时设置SERVER_CRT+SERVER_KEY(确保 Fiber 启用 HTTPS);否则会发生协议不匹配并导致 FRP 无法工作,程序会在启动阶段直接报错退出。 - ❗配置约束:当同时设置
SERVER_CRT+SERVER_KEY(Fiber 仅提供 HTTPS)且FRP_ENABLED=true时,FRP_HTTPS不能为false。否则 FRP 会以 HTTP 回源到 HTTPS 本地服务,发生协议不匹配并导致 FRP 无法工作,程序会在启动阶段直接报错退出。
FRP_SCHEME_HTTPS:生成的公网 URL scheme(默认true)false:公网 URL 为http://<random>.<FRP_BASE_DOMAIN>true:公网 URL 为https://<random>.<FRP_BASE_DOMAIN>
常见配置示例:
-
使用官方公共服务 bilirec.org(推荐)
FRP_ENABLED=true # 只开启 FRP_ENABLED=true 时,就会使用官方免费的公共 FRP 服务 -
自定义 FRP(需要自己提供服务器信息)
FRP_ENABLED=true FRP_SERVER=your-frp-ip:7000 FRP_TOKEN=your-token # 你的 FRP 服务需要什么,就按你的服务填写 FRP_BASE_DOMAIN=your-domain.com FRP_HTTPS=false # 内部用 HTTP FRP_SCHEME_HTTPS=true # Caddy 或前置代理提供 HTTPS
FRP enabled in official-public mode
FRP enabled in custom-selfhost mode
official-public:当前使用官方免费的公共 FRP 服务custom-selfhost:当前使用自定义 FRP 配置
使用 FRP 后,你可以把本机的 Bilirec 系统暴露到外网。这样无论你在家里、办公室,还是外出时,只要打开 Web 界面,就能随时登录和管理自己的录制任务、查看录制状态、处理文件与通知。
export BILIBILI_LOGIN_MODE=controller
# 可选:指定监听网卡;留空表示监听所有网卡
# export HOST=0.0.0.0
export PORT=8080
# 可选:启用 HTTPS(当两者都设置时,Fiber 默认启用 HTTPS)
# export SERVER_CRT=/path/to/server.crt
# export SERVER_KEY=/path/to/server.key
# 可选:FRP 内网穿透(只开启 FRP_ENABLED=true 时默认走官方公共服务)
export FRP_ENABLED=true
export FRP_SERVER=tunnel.bilirec.org:7000
# 若要自定义 FRP 服务,再设置 FRP_SERVER / FRP_BASE_DOMAIN / FRP_TOKEN
export FRP_BASE_DOMAIN=tunnel.bilirec.org
export TRUSTED_PROXIES=161.33.159.26 # 受信任代理 IP,默认值为公共 FRP 服务 IP;多个以逗号分隔
export FRP_HTTPS=false # 使用 HTTP 代理
export FRP_SCHEME_HTTPS=true # 公网 URL 为 HTTPS
export MAX_CONCURRENT_RECORDINGS=3
export MAX_RECORDING_HOURS=10
export MAX_RECOVERY_ATTEMPTS=5
export MAX_RETRY_MINUTES=10
export OUTPUT_DIR=/path/to/records
export SECRET_DIR=/path/to/secrets
export NO_CONVERT_IF_INVALID=false
export DATABASE_DIR=/path/to/database
export CONVERT_TO_MP4=false
export DELETE_SOURCE_AFTER_CONVERT=false
# 可选:CloudConvert(如果启用会对大文件使用云端转换)
export CLOUDCONVERT_THRESHOLD=1073741824
export CLOUDCONVERT_API_KEY=
export CLOUDCONVERT_CHECK_INTERVAL_SECS=180
export CLOUDCONVERT_MAX_CONCURRENT_DOWNLOADS=1
# FFmpeg 本地转换参数
export FFMPEG_CHECK_INTERVAL_SECS=60
export FFMPEG_MAX_CONCURRENT_TASKS=1
export FFMPEG_ALLOW_DURING_RECORDING=false
export FFMPEG_ALLOW_DURING_RECORDING_MAX_ACTIVE_RECORDINGS=1
export PUBLIC_BASE_URL=http://localhost:8080
export FRONTEND_URL=http://localhost:8080
export WEBPUSH_SUBSCRIBER=mailto:webpush@example.com
export UPLOAD_BUFFER_SIZE=5242880
export DOWNLOAD_BUFFER_SIZE=5242880
export STREAM_WRITER_BUFFER_SIZE=1048576
export LIVE_STREAM_WRITER_BUFFER_SIZE=8388608
export LIVE_STREAM_WRITER_SYNC_PERIOD_SECS=0
export LIVE_STREAM_WRITER_FLUSH_PERIOD_SECS=10
export LIVE_STREAM_WRITER_CHAN_BUFFER_SIZE=64
export LIVE_STREAM_WRITER_BYTES_POOL_SIZE=524288
export SKIP_SMALL_FLUSH=true
export JWT_SECRET=bilirec_secret
export DEBUG=false
# 可选:启用 REST API 认证
export USERNAME=admin
export PASSWORD=changeme
export PRODUCTION_MODE=false如果你是使用二进制文件,启动服务后会生成 .env 文件,里面包含当前的环境变量配置(不包含敏感信息)。你可以编辑这个文件来修改配置,或者直接设置环境变量覆盖。
PUBLIC_BASE_URL 为空或不是有效 URL(必须包含 http/https)时,预签名 URL 会记录 warning 并回退为不含 base URL 的相对地址(/files/tempdownload?presigned=...);CloudConvert 在该情况下会直接报错并拒绝创建任务。
Web Push 的 VAPID key 会由后端在启动时自动生成并写入 SECRET_DIR(默认 secrets)下的 _webpush_public_key 与 _webpush_private_key。后续重启会优先复用已存在的 key。
Bilirec 当前默认配置已针对树莓派 5B + microSD 场景优化,在“降低写入次数”和“控制内存占用”之间取平衡:
| 优化项 | 说明 |
|---|---|
LIVE_STREAM_WRITER_SYNC_PERIOD_SECS=0 |
禁用周期性 fsync(最昂贵的操作),数据仅在录制结束时同步到磁盘,减少 I/O 突峰。权衡:意外断电可能丢失最后一段尚未持久化的数据。 |
LIVE_STREAM_WRITER_BUFFER_SIZE=8388608 (8MB) |
8MB 进一步降低 flush 频率,优先降低 SD 卡写入频次。@1080p30fps (4.5Mbps) 约每 14.2 秒触发一次满缓冲写入。 |
LIVE_STREAM_WRITER_FLUSH_PERIOD_SECS=10 |
将周期性 flush 改为 10 秒,优先降低 SD 卡磨损;代价是异常断电时未 flush 数据窗口会略增。 |
LIVE_STREAM_WRITER_CHAN_BUFFER_SIZE=64 |
控制在途内存占用。以 512KB chunk 估算,单录制任务约 32MB 队列数据;比 128(约 64MB)更适合 4GB 设备。 |
SKIP_SMALL_FLUSH=true |
启用小块跳过保护,若录制总写入量 < 缓冲区大小则跳过 flush。特别有效于低比特率流(如 240p),防止多次小块写入磨损。 |
LIVE_STREAM_WRITER_BYTES_POOL_SIZE=524288 (512KB) |
与常见 stream chunk 大小一致,减少额外分配与拷贝。 |
SEQUENTIAL_WRITE=true |
启用全局 flush 锁,序列化多路录制的写入操作,多路并发时有效降低 I/O 峰值; 默认启用以保护 microSD。 |
MAX_CONCURRENT_RECORDINGS=3 |
保守并发上限,直接限制 RAM 峰值。 |
容器运行时默认值也同步为树莓派 5B 取向:GOMEMLIMIT=768MiB、GOGC=100。
默认配置主要为降低单板电脑(如树莓派)的 microSD 卡磨损而设计,采取了极度保守的缓冲与刷盘策略。如果你的存储介质是常规 HDD、SSD 或是自建的本地 NAS,具备更好的读写寿命和性能,建议将侧重点从“保护存储”转移到“提高录制完整性与多路并发能力”。
请根据你的具体硬件情况选择以下配置方向:
SSD 具备极高的随机读写性能,无需过度担忧小块文件的写入磨损,且开启应用层的写入序列化反而会限制其并发优势。
# 解放 I/O 限制,优先保障数据实时性与高并发
export LIVE_STREAM_WRITER_SYNC_PERIOD_SECS=30 # 恢复周期性 sync,降低断电导致的数据丢失风险
export SKIP_SMALL_FLUSH=false # 优先保障录制完整性,不再跳过小块 flush
export SEQUENTIAL_WRITE=false # 【关键】禁用全局 flush 锁,解放 SSD 多路并发 I/O 能力
export LIVE_STREAM_WRITER_CHAN_BUFFER_SIZE=128 # 提高内存队列深度(容忍更高的网络突发流量)
export MAX_CONCURRENT_RECORDINGS=10 # 可视设备的 CPU 与内存宽裕程度放宽限制
机械硬盘(如 16TB 级别的存储盘)的物理弱点是磁头寻道延迟。当发生多路并发录制时,频繁的随机写入会导致严重的卡顿。调整核心在于在内存中合并大块数据并强制顺序写入。
# 优化大块连续写入,保护磁头并提升系统整体流畅度
export LIVE_STREAM_WRITER_SYNC_PERIOD_SECS=45 # 适当拉长 sync 周期,避免频繁打断顺序写入
export SKIP_SMALL_FLUSH=false
export SEQUENTIAL_WRITE=true # 【关键】保持全局写锁,多路并发时强制转换为顺序写入,大幅降低物理寻道延迟
export LIVE_STREAM_WRITER_BUFFER_SIZE=16777216 # (16MB) 增大单次落盘大小,让文件在磁盘上的分布更连续
export MAX_CONCURRENT_RECORDINGS=5 # 视硬盘转速适度调整
Tip
关于自建 NAS 与混合部署
- 文件系统缓存(如 ZFS): 如果你的底层存储池使用了 ZFS 这种自带强大内存缓存机制(ARC)的文件系统,频繁的应用层
sync可能会引发写入放大并拖慢效能。此时可以将LIVE_STREAM_WRITER_SYNC_PERIOD_SECS设为0或更长的时间(如60),将落盘调度完全交由底层系统接管。 - 与私有云服务共存: 若你的硬盘同时还在运行其他个人服务(如 Cloudreve 私有云、影音服务器等),强烈建议保持
SEQUENTIAL_WRITE=true。这能有效避免 bilirec 的碎片化写入与其他服务发生磁头争抢,保障 NAS 在私人使用时的整体响应速度。
Swagger UI 会在服务器运行时于根路径
/提供 — 在浏览器中打开该地址即可查看与测试 API。
如果设置了 USERNAME 与 PASSWORD,REST API 会启用基于 JWT 的认证(登录会在 cookie 中设置 jwtToken)。使用:
POST /login
Content-Type: application/json
{ "user": "<username>", "pass": "<password>" }登录成功后会在响应中设置 JWT cookie(键名 jwtToken),随后对需要认证的接口请携带该 cookie。若未设置用户名/密码,API 默认为公开访问。
当 BILIBILI_LOGIN_MODE=controller 时,可以通过 /auth/bilibili 相关接口在服务运行后触发扫码登录。
-
GET /auth/bilibili/status- 用途:查询当前认证状态(
idle/awaiting_qr/authenticating/authenticated/failed等) - 返回:是否已登录、账号信息、二维码 URL(若已生成)、最近错误(若有)
- 响应示例(已登录):
{ "authenticated": true, "state": "authenticated", "account": { "mid": 123456789, "uname": "bilibili_user" } } - 响应示例(等待扫码):
{ "authenticated": false, "state": "awaiting_qr", "qr": { "url": "https://api.bilibili.com/x/web-interface/qrcode/generate?qrcode_key=abc123..." } }
- 用途:查询当前认证状态(
-
POST /auth/bilibili/init- 用途:创建新的二维码登录会话(支持账号切换,即使已登录也可重新发起登录)
- 成功:
201 Created - 错误:
400 Bad Request(当前不在 controller 模式) /409 Conflict(已存在未完成的登录会话) /500 Internal Server Error(获取二维码失败) - 响应示例(成功):
{ "qr": { "url": "https://api.bilibili.com/x/web-interface/qrcode/generate?qrcode_key=def456..." } } - 响应示例(400 Bad Request):
{ "error": "后端没有启用 controller 模式" } - 响应示例(500 Internal Server Error):
{ "error": "failed to get QR code: ..." }
典型流程:
- 先
POST /login(如果你启用了USERNAME/PASSWORD) - 调用
POST /auth/bilibili/init获取二维码链接 - 用户扫码后,轮询
GET /auth/bilibili/status,直到state=authenticated
账号切换流程:已登录用户可随时调用 POST /auth/bilibili/init 发起新的登录会话,扫码后会切换到新的账号。
示例(使用 cookie 持久化会话):
# 1) 登录(启用了 USERNAME/PASSWORD 时需要)
curl -i -c cookies.txt -X POST http://127.0.0.1:8080/login \
-H "Content-Type: application/json" \
-d '{"user":"admin","pass":"changeme"}'
# 2) 初始化二维码登录
curl -i -b cookies.txt -X POST http://127.0.0.1:8080/auth/bilibili/init
# 3) 查询登录状态
curl -s -b cookies.txt http://127.0.0.1:8080/auth/bilibili/status-
开始录制
POST /record/:roomID/start?duration_minutes=<N>&stream_profile=<profile>
duration_minutes为可选 query 参数:值 说明 不传 使用系统预设( MAX_RECORDING_HOURS)0使用系统预设( MAX_RECORDING_HOURS)(同不传)-1无限录制,不自动停止 N(正整数)N 分钟后自动停止 stream_profile也是可选 query 参数,用于优先指定录制流格式:值 说明 不传 自动选择可用流(默认行为) http-flv仅使用 HTTP-FLV 录制 hls-ts仅使用 HLS-TS 录制 hls-fmp4仅使用 HLS-fMP4 录制 -
停止录制
POST /record/:roomID/stop
-
批量获取录制状态
POST /record/statuses Content-Type: application/json { "roomIDs": [123, 456, 789] }
也支持通过查询参数传入(
roomIDs=123,456,789)。响应示例:
{ "123": "recording", "456": "idle", "789": "stopped" } -
批量获取录制统计
POST /record/stats Content-Type: application/json { "roomIDs": [123, 456, 789] }
也支持通过查询参数传入(
roomIDs=123,456,789)。响应示例:
{ "123": { "bytes_written": 1048576, "status": "recording", "start_time": 1234567890, "elapsed_seconds": 120, "output_path": "records/tester-123/live-title.flv" }, "456": { "bytes_written": 0, "status": "idle", "start_time": 0, "elapsed_seconds": 0, "output_path": "" } } -
列出所有录制任务
GET /record/list
-
列出文件
GET /files/browse/*
支持以下查询参数:
参数 说明 默认值 offset分页偏移量(跳过前 N 条) 0limit每页最大条数(0 表示不限制,最大 200) 0search按文件名搜索(大小写不敏感) (不过滤) 返回
PagedTree对象:{ "total": 42, "items": [ { "name": "title-20260428.flv", "is_dir": false, "path": "room123/title-20260428.flv", "size": 1048576 } ] }total为过滤/搜索后的总条数(不受offset/limit影响),items为当前页的文件列表。 -
下载文件
GET /files/download/*
下载接口直接返回存储的文件。 若要将录制的源文件(如 FLV/TS)转为 MP4,请启用
CONVERT_TO_MP4:在录制完成时,recorder 会将可转换文件加入转换队列,由后台任务异步转换为 MP4(转换行为受DELETE_SOURCE_AFTER_CONVERT控制)。 当同时设置了CLOUDCONVERT_API_KEY且文件大小 >=CLOUDCONVERT_THRESHOLD(默认 1 GB)时,系统会优先使用 CloudConvert(异步任务,可通过/convert/tasks查询转换状态);否则由本地 ffmpeg 后台任务处理。 -
临时 / 预签名下载(Presigned)
POST /files/presigned/{path}?ttl=<seconds>
该接口需要身份认证(JWT),用于为文件创建一个临时的预签名下载令牌(
ttl可选,单位秒,默认 3600)。成功创建后会返回包含临时令牌或 URL 的响应。使用该令牌可以进行匿名下载:GET /files/tempdownload?presigned=<token>
GET /files/tempdownload无需认证,但必须提供有效的presigned查询参数。该临时链接会在创建时设置过期时间,过期后将无法使用。 -
删除多个文件
DELETE /files/batch
请求体:JSON 数组,包含要删除的相对文件路径,示例:
["room123/20250101.flv", "room456/20250102.flv"]
-
删除目录
DELETE /files/{path}
-
在线播放视频
GET /files/playback/{path}
在浏览器中直接播放已录制的 MP4 视频(VOD)。该接口返回视频流,浏览器会直接在网页中显示播放器而非下载文件。
支持的格式:
video/mp4- MP4 格式视频
使用示例:
<video controls width="100%"> <source src="/files/playback/username-roomID/20250101.mp4" type="video/mp4"> Your browser does not support HTML5 video. </video>
注意:
- 只支持 MP4 的文件(可启用
CONVERT_TO_MP4后转换得到) - 无法播放正在进行中的录制文件
- 支持浏览器的 Range 请求(可快进/快退)
-
列出进行中的转换任务
GET /convert/tasks
-
取消转换任务
DELETE /convert/tasks/:task_id
返回
204 No Content表示取消成功,若任务不存在返回404。
-
获取房间信息
GET /room/:roomID/info
获取指定直播房间的详细信息。返回房间的基本信息如标题、粉丝数、直播封面等。
响应示例:
{ "room_id": 123, "short_id": 456, "title": "房间标题", "description": "房间描述", "live_status": 1, "followers": 10000, "cover": "https://example.com/cover.jpg" }- 状态
200: 成功获取房间信息 - 状态
400: 无效的房间 ID - 状态
404: 房间不存在 - 状态
500: 服务器内部错误
- 状态
-
批量获取房间信息
POST /room/infos Content-Type: application/json { "roomIDs": [123, 456, 789] }
支持两种传参方式:
- Query 参数:
POST /room/infos?roomIDs=123,456,789 - JSON payload:
{"roomIDs":[123,456,789]}(字段名也可使用room_ids或roomIds)
响应为房间 ID 到房间信息的映射:
{ "123": { "room_id": 123, "title": "标题1", ... }, "456": { "room_id": 456, "title": "标题2", ... } } - Query 参数:
-
检查直播状态(单个房间)
GET /room/:roomID/live
检查指定房间的直播是否进行中。返回:
{ "room_id": 123, "is_live": true }- 状态
200: 成功获取直播状态 - 状态
400: 无效的房间 ID - 状态
404: 房间不存在 - 状态
500: 服务器内部错误
- 状态
-
检查多个房间的直播状态
POST /room/lives
批量检查多个房间的直播状态。
支持两种传参方式:
- Query 参数:
POST /room/lives?roomIDs=123,456,789 - JSON payload:
{"roomIDs":[123,456,789]}(字段名也可使用room_ids或roomIds)
响应示例:
{ "123": true, "456": false, "789": true } - Query 参数:
-
订阅房间
POST /room/:roomID
订阅一个直播房间,当房间开播时会接收更新。
- 状态
200: 订阅成功 - 状态
409: 已订阅此房间 - 状态
400: 无效的房间 ID
- 状态
-
取消订阅
DELETE /room/:roomID
取消订阅直播房间。
- 状态
200: 取消订阅成功 - 状态
404: 未订阅此房间 - 状态
400: 无效的房间 ID
- 状态
-
检查订阅状态
GET /room/subscribe/:roomID
检查是否已订阅指定房间。返回:
{ "room_id": 123, "is_subscribed": true } -
列出所有订阅房间
GET /room/subscribe
获取所有已订阅房间的 ID 列表。返回:
{ "room_ids": [123, 456, 789] } -
获取房间配置
GET /room/:roomID/config
获取指定房间的配置(自动录制、通知等)。返回:
{ "room_id": 123, "auto_record": true, "notify": true, "record_duration_minutes": 120 } -
更新房间配置
PUT /room/:roomID/config
更新房间的配置(自动录制、通知等)。请求体:
{ "auto_record": true, "notify": true, "record_duration_minutes": 120 }record_duration_minutes为可选字段,仅在auto_record: true时生效,作为自动录制的时长上限:值 说明 0(默认)使用系统预设( MAX_RECORDING_HOURS)-1无限录制,不自动停止 N(正整数)N 分钟后自动停止 ![NOTE] 如果你想让这个录制时长套用到手动触发的录制任务,请自行在调用
/record/:roomID/start时传入相同的duration_minutes参数;否则手动录制将不受此配置影响,仍然使用系统预设或你在启动录制时指定的时长。
-
获取 Web Push 公钥
GET /notify/public-key
获取前端订阅 Web Push 所需的 VAPID 公钥。
-
注册 Web Push 订阅
POST /notify/subscribe Content-Type: application/json
请求体为浏览器 Push API 产生的 subscription JSON(包含
endpoint与keys)。 -
取消 Web Push 订阅
DELETE /notify/subscribe
可通过 query 参数
endpoint或 JSON body 提供 endpoint。 -
SSE 实时订阅(Android backend / libbilirec.so 推荐)
GET /notify/sse?token=YOUR_NOTIFY_SSE_TOKEN Accept: text/event-stream
- 该端点用于无法依赖 Google FCM 的场景(例如中国大陆网络环境),可由 Android 后端应用通过 SSE 获取通知,再由 Android 创建本地通知。
token必填,来源于环境变量NOTIFY_SSE_TOKEN。- 当
NOTIFY_SSE_TOKEN为空时,SSE 端点返回503(禁用状态)。 - 建议通过 HTTPS 使用 SSE,避免 token 在传输链路中明文暴露。
- 启用调试:设置环境变量
DEBUG=true启用调试模式,服务器启动时会在日志中打印一个临时十六进制令牌(hex token)。 - pprof 性能分析:调试模式下会在
/debug/pprof挂载 pprof 以便性能分析。该路由受保护:可以在请求头Authorization中填入启动日志中显示的 hex 令牌来访问。 - 实现参考:该逻辑位于
internal/modules/rest/rest.go中(DEBUG控制是否启用,令牌授权访问)。
.
├── .github/ # CI / workflows
├── Dockerfile
├── go.mod
├── LICENSE
├── README.md
├── main.go
├── swagger.go
├── dotenv.go
├── main_test.go
├── internal/ # 内部包(不对外暴露)
│ ├── controllers/ # HTTP 控制器
│ │ ├── convert/ # 转换任务管理
│ │ ├── file/ # 文件管理
│ │ ├── notify/ # 实时通知(Web Push + SSE)
│ │ ├── record/ # 录制管理
│ │ └── room/ # 房间信息与订阅
│ ├── modules/ # 核心模块
│ │ ├── bilibili/ # Bilibili API 封装与认证
│ │ ├── config/ # 配置管理
│ │ └── rest/ # REST 服务
│ ├── processors/ # 流处理器
│ └── services/ # 业务逻辑服务
│ ├── convert/ # 转换服务
│ ├── file/ # 文件操作
│ ├── notify/ # 实时通知服务
│ ├── path/ # 路径管理
│ ├── recorder/ # 直播录制
│ ├── room/ # 房间信息与订阅
│ ├── stream/ # 流处理
│ ├── subcheck/ # 订阅检查与自动录制
│ └── subscribe/ # 房间订阅管理
├── pkg/ # 可复用库与工具
│ ├── cloudconvert/ # CloudConvert API 客户端
│ ├── db/ # 数据库抽象层
│ ├── ds/ # 数据结构
│ ├── flv/ # FLV 格式处理
│ ├── hls/ # HLS 格式处理
│ ├── fp/ # 函数式编程工具(maps、slices)
│ ├── monitor/ # 监控与统计
│ ├── pipeline/ # 流处理管道
│ ├── pool/ # 内存池
│ ├── signeddownload/ # 预签名下载
│ └── swr/ # Stale-While-Revalidate 缓存
└── utils/ # 工具函数库
- 通过
bilibili.Client获取直播流地址(默认自动选择可用 profile,也可按需指定stream_profile) - 使用
stream.Service读取流数据 recorder.Service管理录制任务(自动重连与恢复)- 数据写入到录制文件(扩展名依流格式可能为
.flv、.ts或.fmp4),保存在配置的输出目录:
-
HTTP-FLV 格式: 当检测到直播 PK 等 FLV 文件头变更时(分辨率切换)录制器会自动轮转到新的分段文件;
-
HLS-TS / HLS-fMP4 格式: 不执行文件轮转,直播 PK 等不连续性由 HLS播放列表层自然处理,录制器仅在流中断时重连并继续写入同一文件。
首个文件名形如 标题-时间戳.ext,后续分段(HTTP-FLV 轮转后)会命名为 标题-时间戳-1.ext、标题-时间戳-2.ext。如果启用了 CONVERT_TO_MP4,每个可转换分段在完成时都会自动加入转换队列,并由后台任务异步转换/封装为 .mp4 文件(转换行为受 DELETE_SOURCE_AFTER_CONVERT 控制,转换任务可通过 /convert/tasks 查询)。
- 自动恢复: 当流中断时自动重连,详见
recorder.Service - 自动分段轮转(HTTP-FLV): 仅适用于 HTTP-FLV 格式;当检测到 FLV 文件头变更(直播 PK 等分辨率切换)时,自动轮转到新的分段文件并重写文件头,降低画质切换导致输出异常的风险。HLS-TS / HLS-fMP4 格式不使用文件轮转,不连续性由 HLS 播放列表层自然处理
- 自动录制: 为订阅的直播间配置自动开播录制,后台定期检查直播间状态并自动启动录制,详见
subcheck.Service - 实时通知: 支持 Web Push 与 SSE 两种通知方式,推送直播开播通知和自动录制状态,详见
notify.Service - 内存池: 使用
pool.BufferPool减少内存分配 - SWR 缓存: 房间信息缓存采用 Stale-While-Revalidate 策略(
pkg/swr),结合 singleflight 去重,缓存命中时立即返回旧数据并在后台异步刷新,软 TTL 5 分钟、硬 TTL 30 分钟,有效降低 Bilibili API 请求压力 - 低资源占用: 设计注重低内存,低SD卡磨損和低 CPU 使用,适合树莓派等资源受限设备
- 文件管理: 支持列出、预览、下载(可转换格式)、批量删除文件及删除目录,详见
internal/controllers/file/file.go - 自动转换: 如果启用
CONVERT_TO_MP4,录制完成时会自动将可转换源文件(例如 FLV、TS)转为 MP4;可通过DELETE_SOURCE_AFTER_CONVERT控制是否删除原始源文件。已修复部分不支持的编解码器导致转换失败的问题,并具备 FFmpeg 任务冷却机制避免卡死失败任务 - 在线播放: 支持在浏览器中直接播放已转换的 MP4 视频,提供原生 HTML5 video 标签体验,支持暂停/快进/全屏等操作
- 实时修复(Realtime Fixer): 在流式写入场景下逐个修复 FLV Tag 的时间戳并输出,包含重复 Tag 去重(可查询去重统计),并通过内存池、去重缓存与周期清理来保持低延迟与低内存占用,适合边录制边推送或实时下载的场景。
- 函数式编程工具: 提供
fp包含便捷的 maps 和 slices 操作函数 - REST API 文档: Swagger UI 在根路径
/提供(由swag生成,参见internal/modules/rest) - 认证与调试: 可选用户名/密码登录(设置
USERNAME和PASSWORD)启用 JWT 认证;调试模式下可通过/debug/pprof访问 pprof(受临时 token 或基本 auth 保护) - Cookie 管理: 自动刷新 Bilibili Cookie 保持登录状态
主要依赖库:
- github.com/gofiber/fiber/v3 - Web 框架
- github.com/CuteReimu/bilibili/v2 - Bilibili API 客户端
- go.uber.org/fx - 依赖注入框架
- github.com/sirupsen/logrus - 日志库
请参阅项目许可证文件。
欢迎提交 Issue 和 Pull Request!






