* fix(stream-gears): write the cached GOP on disconnect, flush before the segment hook The httpflv loop kept the current GOP in `flv_tags_cache` and only wrote it when the next keyframe arrived. On EOF, a 30 s read timeout or a parse error the function returned and the cache was dropped, so every disconnect, reconnect and stream end lost the last 1-10 s. The loop now runs in an inner function; whatever way it exits, the complete tags in the cache are written before the original result (including the error) is returned. `FlvFile` / `TsFile` renamed the `.part` file and ran the segment hook before the old `BufWriter` was flushed, so the hook (FileValidator, upload) could see a truncated file, and a failed final flush (disk full) was swallowed. Both now go through `finish()`: flush and check the error, then rename, then run the hook. `create_new` returns the flush error; `Drop` logs it with `error!`. Tests: the rolling-segment test now expects 120 keyframes on disk (was 119) and checks the size seen by the hook equals the final size; new cases for a stream that ends cleanly, a read that times out and a parse error; a TS hook size test; a failed-flush test. Ref #1726 * feat(recorder): default to mesio when no downloader is configured Rooms without a `downloader` setting followed the plugin hint, which is stream-gears for almost every platform. They now use mesio: its FLV output carries `onMetaData.keyframes` and every segment starts at timestamp 0, so players can seek; it also records HEVC / Enhanced-FLV and HLS fMP4, which stream-gears cannot. Only the hint mapping in `core/live.rs` changes; an explicit `stream-gears` keeps using stream-gears, and plugins that hint streamlink / yt-dlp are unaffected. `DownloaderRuntime::from_type` no longer has a catch-all arm. `ffmpeg-external` and `ffmpeg-internal` used to fall into it and silently run stream-gears; both now run ffmpeg (external segmenting, same as `ffmpeg`). The arm for streamlink / yt-dlp / ytarchive, which need the live stream and are built in `downloader_runtime`, is listed explicitly and logs a warning. WebUI, README, sample configs and CHANGELOG now say mesio is the default and list the behaviour differences: the container follows the source (HLS fMP4 is saved as .mp4), files are written under their final name without `.part`, and Douyu Wangsu reconnects leave a 2.6-5.8 s overlap between segments. Ref #1726 * feat(mesio): pass segment duration and size to the segment callback mesio's segment-complete callback reports the duration and byte size of each closed file, but biliup only logged them and built `SegmentInfo` with the path and index. `SegmentInfo` gains `duration_secs` / `size_bytes` (None for the other downloaders) and mesio fills them in. The end-to-end mesio test checks the reported size equals the file size on disk. Ref #1726 * fix(recorder): parse segment_time the same way in every downloader `segment_time` was read three different ways. stream-gears indexed `parts[1]` / `parts[2]` directly, so "3600" or "01:00" panicked the download task (and an unparsable hour silently became 1); mesio returned 0 for anything that was not HH:MM:SS and silently recorded without time splits; ffmpeg got the raw string and accepted both forms. `parse_segment_time` follows ffmpeg's duration syntax (`[HH:]MM:SS[.frac]` or plain seconds; minutes and seconds below 60 when colons are used), so all three downloaders now agree. stream-gears and mesio share `DownloadConfig::segment_time_limit`: 0 means no time split for both (mesio already did this; stream-gears used to split at every keyframe), and a value that still cannot be parsed logs a warning and records without time splits instead of panicking or being ignored silently. The time-range clamp uses the same parser, so "3600" is shortened near the window end like "01:00:00". Ref #1726 * feat(webui): enter file_size as a number with an MB/GB unit The config still stores bytes. The control converts with 1024 (the same sizes Windows Explorer shows), keeps an untouched value byte-exact even when it is not a round MB/GB, and re-picks the unit when the form value is replaced from outside. In the streamer override, clearing a value that was already set now saves null (no size split for that streamer); before, Semi dropped the emptied key and the old value stayed. Clearing something typed in the same session still falls back to inheriting the global setting. * fix(webui): keep the file_size unit beside the number in narrow modals
417 lines
17 KiB
TypeScript
417 lines
17 KiB
TypeScript
'use client'
|
||
import React from 'react'
|
||
import styles from '../../styles/dashboard.module.scss'
|
||
import SectionTitle from '../../(app)/components/SectionTitle'
|
||
import { Form, Select, Space, useFormState } from '@douyinfe/semi-ui'
|
||
import { IconUpload, IconDownload } from '@douyinfe/semi-icons'
|
||
import { FileSizeField } from '../FileSizeInput'
|
||
|
||
const Global: React.FC = () => {
|
||
// useFormApi 不订阅表单值变化,切换下拉框后条件渲染不会刷新;useFormState 会
|
||
const { values } = useFormState()
|
||
const isSyncDownloader = values?.downloader === 'sync-downloader'
|
||
|
||
return (
|
||
<>
|
||
{/* 全局下载 */}
|
||
<div className={styles.frameDownload}>
|
||
<SectionTitle icon={<IconDownload size="small" />} title="全局下载设置" />
|
||
<Form.Select
|
||
label="下载插件(downloader)"
|
||
field="downloader"
|
||
placeholder="mesio(默认)"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
全局默认的下载插件,可在单个主播的覆写设置里另选。可选:
|
||
<br />
|
||
1. <strong>mesio</strong>(默认):内置 rust-srec 引擎,无需额外安装。进程内下载 FLV / HLS,修复时间戳(每段从
|
||
0 开始)、写入关键帧索引(onMetaData.keyframes),支持 HEVC 和 hls_fmp4;不转封装,按源站的容器保存(FLV /
|
||
TS,hls_fmp4 存为 .mp4)。详见{' '}
|
||
<a
|
||
href="https://github.com/hua0512/rust-srec"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
style={{ color: 'var(--semi-color-link)' }}
|
||
>
|
||
项目主页
|
||
</a>
|
||
。
|
||
<br />
|
||
2. ffmpeg:非 Docker 用户需自行安装 FFmpeg。
|
||
<br />
|
||
3. streamlink:多线程下载 HLS 分片,也可下载 FLV 直链;需系统中有 streamlink 命令。
|
||
<br />
|
||
4. sync-downloader(边录边传):录制的同时流式上传,<strong>需先为主播设置上传模板</strong>;不受
|
||
pool2 / threads / segment_time 控制,固定 3 线程上传,请确保上传带宽充足;非 Docker 用户需自行安装
|
||
FFmpeg。详见 Wiki{' '}
|
||
<a
|
||
href="https://github.com/biliup/biliup/wiki/%E8%BE%B9%E5%BD%95%E8%BE%B9%E4%BC%A0%E5%8A%9F%E8%83%BD"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
style={{ color: 'var(--semi-color-link)' }}
|
||
>
|
||
边录边传功能
|
||
</a>
|
||
。
|
||
<br />
|
||
5. ytarchive:仅适用于 YouTube 直播。
|
||
<br />
|
||
6. stream-gears:内置,无需额外安装,可防 FLV 流花屏;不支持 HEVC 编码和 hls_fmp4 流,FLV
|
||
时间戳沿用源站的原值(不从 0 开始)。
|
||
</div>
|
||
}
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
>
|
||
<Select.Option value="streamlink">streamlink(hls多线程下载)</Select.Option>
|
||
<Select.Option value="ffmpeg">ffmpeg</Select.Option>
|
||
<Select.Option value="stream-gears">stream-gears</Select.Option>
|
||
<Select.Option value="sync-downloader">sync-downloader(边录边传)</Select.Option>
|
||
<Select.Option value="ytarchive">ytarchive(仅适用于 Youtube Live)</Select.Option>
|
||
<Select.Option value="mesio">mesio(默认)</Select.Option>
|
||
</Form.Select>
|
||
{isSyncDownloader ? (
|
||
<>
|
||
<Form.Input
|
||
field="sync_save_dir"
|
||
label="边录边传额外保存本地目录(sync_save_dir)"
|
||
placeholder=""
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
rules={[
|
||
{
|
||
pattern: /^[^*|?"<>]*$/,
|
||
message: '路径中不能包含Windows不允许的字符 * | ? " < >',
|
||
},
|
||
{
|
||
pattern: /^(?![a-zA-Z]:).*$/,
|
||
message: '以字母开头时,第二个字符不能是中文冒号',
|
||
},
|
||
{
|
||
pattern: /^[^:]*$|^[a-zA-Z]:[\/\\][^:]*$/,
|
||
message: '冒号只能出现在第二个字符位置,且后面必须连接斜杠',
|
||
},
|
||
{
|
||
pattern: /^(?!.*?\.{3,})(?!.*?\.{2}(?![\/\\])).*$/,
|
||
message: '点号最多只能连续出现两次,且后面必须连接斜杠',
|
||
},
|
||
{
|
||
pattern: /^(?!.*\/\\)(?!.*\\\/).*$/,
|
||
message: '不允许连接正反斜杠',
|
||
},
|
||
{
|
||
pattern: /^(?!.*([\\]{3,}|[\/]{2,})).*$/,
|
||
message: '反斜杠最多只能连续出现两次,正斜杠最多只能连续出现一次',
|
||
},
|
||
]}
|
||
stopValidateWithError={true}
|
||
/>
|
||
</>
|
||
) : null}
|
||
<FileSizeField
|
||
label="视频分段大小(file_size)"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
录像单文件大小上限,超过后开始写下一个文件。下载回放时无法使用。留空表示不按大小分段。
|
||
<br />
|
||
按 1024 进制换算:1 GB = 1024 MB = 1073741824 字节,与 Windows 资源管理器显示的大小一致。配置文件里存的仍是字节数。
|
||
</div>
|
||
}
|
||
field="file_size"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.Input
|
||
field="segment_time"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
录像单文件时间限制,超过此时长触发文件分割。
|
||
<br />
|
||
格式:'00:00:00'(时:分:秒)
|
||
</div>
|
||
}
|
||
label="视频分段时长(segment_time)"
|
||
placeholder="01:00:00"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
rules={[
|
||
{
|
||
pattern: /^[^:]*$/,
|
||
message: '请使用英文冒号',
|
||
},
|
||
{
|
||
pattern: /^[0-9:]*$/,
|
||
message: '只接受数字和英文冒号',
|
||
},
|
||
{
|
||
pattern: /^$|^[0-9]{2,4}:[0-5][0-9]:[0-5][0-9]$/,
|
||
message: '分或秒不符合规范',
|
||
},
|
||
]}
|
||
stopValidateWithError={true}
|
||
/>
|
||
<Form.Input
|
||
field="filename_prefix"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
全局文件名模板。可被单个主播文件名模板覆盖。可用变量如下
|
||
<br />
|
||
{'\u007B'}streamer{'\u007D'}: 录播备注(必须保留)
|
||
<span style={{ margin: '0 20px' }}></span>
|
||
{'\u007B'}title{'\u007D'}: 直播标题
|
||
<br />
|
||
%Y-%m-%d %H_%M_%S: 开始录制时的 年-月-日 时_分_秒
|
||
</div>
|
||
}
|
||
label="文件名模板(filename_prefix)"
|
||
placeholder="{streamer}%Y-%m-%dT%H_%M_%S"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
<Form.Switch
|
||
field="segment_processor_parallel"
|
||
extraText={<div style={{ fontSize: '14px' }}>开启后无法保证分段后处理先后执行顺序</div>}
|
||
label="视频分段后处理并行(segment_processor_parallel)"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.InputNumber
|
||
field="filtering_threshold"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
小于此大小的视频文件将会被过滤删除。
|
||
<br />
|
||
单位:MB
|
||
</div>
|
||
}
|
||
label="碎片过滤(filtering_threshold)"
|
||
suffix={'MB'}
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
|
||
<Form.InputNumber
|
||
field="delay"
|
||
label="下播延迟检测(delay)"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
当检测到主播下播后,延迟一定时间再次检测确认,避免特殊情况提早启动上传导致分稿件。
|
||
<br />
|
||
单位:秒
|
||
<br />
|
||
默认延迟时间为 0 秒
|
||
</div>
|
||
}
|
||
placeholder="0"
|
||
suffix="s"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
<Form.InputNumber
|
||
field="event_loop_interval"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
单个主播检测间隔时间,单位:秒。比如虎牙有10个主播,每个主播会间隔10秒检测
|
||
<br />
|
||
单位:秒
|
||
</div>
|
||
}
|
||
label="直播事件检测间隔(event_loop_interval)"
|
||
suffix="s"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
<Form.InputNumber
|
||
field="pool1_size"
|
||
extraText="负责下载事件的线程池大小,用于限制最大同时录制数。"
|
||
label="下载线程池大小(pool1_size)"
|
||
placeholder={5}
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
<Form.Select
|
||
field="preview_transport"
|
||
label="直播预览取流方式(preview_transport)"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
<div>
|
||
<strong>经 biliup 中转</strong>(默认):页面里的预览复用正在录制的那一路流,不向直播平台多拉一路。
|
||
浏览器与 biliup 在同一台机器或同一内网时选这个,不多占 CDN 带宽。
|
||
</div>
|
||
<div>
|
||
<strong>浏览器直连 CDN</strong>:biliup 向平台另取一条直链(新 token,不影响录制那条),浏览器自己去
|
||
CDN 拉,媒体流量不经过 biliup,适合 biliup 部署在异地服务器、浏览器远程访问的情况,省服务器出口带宽。
|
||
只有 CDN 放行跨域的平台能直连(B 站 / 抖音 / 斗鱼 / 虎牙,FLV 与 HLS 都行);Twitch 等按 Origin 白名单放行
|
||
的平台在直连模式下自动回落中转并在播放器角标标出原因。
|
||
</div>
|
||
</div>
|
||
}
|
||
placeholder="经 biliup 中转(relay)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
>
|
||
<Form.Select.Option value="relay">经 biliup 中转(relay)</Form.Select.Option>
|
||
<Form.Select.Option value="direct">浏览器直连 CDN(direct)</Form.Select.Option>
|
||
</Form.Select>
|
||
</div>
|
||
|
||
<Space />
|
||
|
||
{/* 全局上传 */}
|
||
<div className={styles.frameUpload}>
|
||
<SectionTitle icon={<IconUpload size="small" />} title="全局上传设置" />
|
||
|
||
<Form.Select
|
||
field="submit_api"
|
||
label="提交接口(submit_api)"
|
||
extraText="B站投稿提交接口,默认为自动选择。"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
>
|
||
<Form.Select.Option value="app">安卓APP(app)</Form.Select.Option>
|
||
<Form.Select.Option value="b-cut-android">BCut安卓APP(b-cut-android)</Form.Select.Option>
|
||
<Form.Select.Option value="web">网页(web)</Form.Select.Option>
|
||
</Form.Select>
|
||
<Form.Select
|
||
field="uploader"
|
||
label="上传插件(uploader)"
|
||
extraText="全局默认上传插件选择。"
|
||
placeholder="biliup-rs"
|
||
noLabel={true}
|
||
style={{ width: '100%', display: 'none' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
initValue="Noop"
|
||
>
|
||
<Form.Select.Option value="bili_web">bili_web</Form.Select.Option>
|
||
<Form.Select.Option value="biliup-rs">biliup-rs</Form.Select.Option>
|
||
<Form.Select.Option value="Noop">Noop(即不上传,但会执行后处理)</Form.Select.Option>
|
||
</Form.Select>
|
||
<Form.Select
|
||
field="lines"
|
||
label="上传线路(lines)"
|
||
extraText="b站上传线路选择,默认为自动模式,可手动切换为alia, bda2, bldsa, tx, txa, estx, akbd"
|
||
placeholder="AUTO(自动,默认)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
>
|
||
<Form.Select.Option value="AUTO">AUTO(自动,默认)</Form.Select.Option>
|
||
<Form.Select.Option value="alia">alia(海外-阿里云)</Form.Select.Option>
|
||
<Form.Select.Option value="bda2">bda2(大陆-百度云)</Form.Select.Option>
|
||
<Form.Select.Option value="bldsa">bldsa(大陆-B站自建)</Form.Select.Option>
|
||
<Form.Select.Option value="tx">tx(大陆-腾讯云)</Form.Select.Option>
|
||
<Form.Select.Option value="txa">txa(海外-腾讯云)</Form.Select.Option>
|
||
<Form.Select.Option value="estx">estx(大陆-B站自建)</Form.Select.Option>
|
||
<Form.Select.Option value="akbd">akbd(大陆-B站自建)</Form.Select.Option>
|
||
</Form.Select>
|
||
<Form.InputNumber
|
||
field="threads"
|
||
placeholder={3}
|
||
extraText="单文件并发上传数,未达到带宽上限时,增大此值可提高上传速度(不要设置过大,部分线路限制为8,如速度不佳优先调整上传线路)"
|
||
label="上传并发(threads)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
<Form.InputNumber
|
||
field="max_upload_limit"
|
||
placeholder={8}
|
||
extraText="录播上传次数上限,防止因意外情况如B站接口抽风、录播本身损坏导致录播反复上传浪费宽带或被B站风控(注:限制是记录在程序上下文中的,重启程序会重置上传次数限制;且为了保证尽量不改动老用户使用逻辑,默认将此值设置为一个较大的值,一般推荐设置为2-3)"
|
||
label="上传重试次数限制(max_upload_limit)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
|
||
<Form.InputNumber
|
||
field="pool2_size"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>负责上传事件的线程池大小。根据实际带宽设置。</div>
|
||
}
|
||
placeholder={3}
|
||
label="上传线程池大小(pool2_size)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.Switch
|
||
field="use_live_cover"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
使用直播间封面作为投稿封面。此封面优先级低于单个主播指定的自定义封面,保存于cover文件夹下,上传后自动删除。
|
||
<br />
|
||
目前支持平台:哔哩哔哩,克拉克拉,Twitch,YouTube。
|
||
</div>
|
||
}
|
||
label="使用直播间封面作为投稿封面(use_live_cover)"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default Global
|