* 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
285 lines
11 KiB
TypeScript
285 lines
11 KiB
TypeScript
'use client'
|
||
import React, { useEffect } from 'react'
|
||
import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
|
||
import PlatformPanel from './PlatformPanel'
|
||
|
||
type Props = {
|
||
entity: any
|
||
list: any
|
||
initValues?: Record<string, any>
|
||
bare?: boolean
|
||
}
|
||
|
||
const Bilibili: React.FC<Props> = props => {
|
||
const { entity, list, initValues, bare } = props
|
||
const formApi = useFormApi()
|
||
|
||
useEffect(() => {
|
||
if (initValues) {
|
||
Object.entries(initValues).forEach(([key, value]) => {
|
||
formApi.setValue(key, value)
|
||
})
|
||
}
|
||
}, [initValues, formApi])
|
||
|
||
return (
|
||
<>
|
||
<PlatformPanel header="哔哩哔哩" itemKey="bilibili" bare={bare}>
|
||
<Form.Select
|
||
allowCreate={true}
|
||
filter
|
||
field="bili_qn"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
录制画质,默认原画。
|
||
<br />
|
||
刚开播时通常只有原画,会先录原画;下载插件为 ffmpeg / streamlink
|
||
时之后每次分段会重新取流并切到所选画质,stream-gears / mesio 整场沿用首次取到的流。
|
||
<br />
|
||
所选画质不存在时录 B 站返回的最接近的次档画质;开启「免登录原画」时录最高画质。
|
||
</div>
|
||
}
|
||
label="画质等级(bili_qn)"
|
||
placeholder="25000(原画)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
rules={[
|
||
{
|
||
pattern: /^\d*$/,
|
||
message: '请仅输入纯数字',
|
||
},
|
||
]}
|
||
showClear={true}
|
||
>
|
||
<Select.Option value={30000}>30000(杜比)</Select.Option>
|
||
<Select.Option value={20000}>20000(4k)</Select.Option>
|
||
<Select.Option value={10000}>10000(原画)</Select.Option>
|
||
<Select.Option value={401}>401(蓝光-杜比)</Select.Option>
|
||
<Select.Option value={400}>400(蓝光)</Select.Option>
|
||
<Select.Option value={250}>250(超清)</Select.Option>
|
||
<Select.Option value={150}>150(高清)</Select.Option>
|
||
<Select.Option value={80}>80(流畅)</Select.Option>
|
||
<Select.Option value={0}>0(最低画质)</Select.Option>
|
||
</Form.Select>
|
||
<Form.Switch
|
||
field="bilibili_danmaku"
|
||
extraText="录制哔哩哔哩弹幕,默认关闭。弹幕保存为与录像同名的 XML 文件,并随录像分段一起分段。"
|
||
label="录制弹幕(bilibili_danmaku)"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.Switch
|
||
field="bilibili_danmaku_detail"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
弹幕文件中额外记录发送者昵称、UID,并保存醒目留言、上舰、礼物记录。默认关闭。
|
||
<br />
|
||
需先开启「录制弹幕」。<strong>实验性功能</strong>:可能与弹幕转 ASS 工具不兼容。
|
||
</div>
|
||
}
|
||
label="录制详细弹幕(bilibili_danmaku_detail)"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.Switch
|
||
field="bilibili_danmaku_raw"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
额外保存 B 站服务器返回的原始弹幕数据,供有技术能力的用户做统计分析。默认关闭。
|
||
<br />
|
||
需先开启「录制弹幕」。<strong>实验性功能</strong>:开启后弹幕文件每 5
|
||
分钟才写入一次,且体积可能非常大。
|
||
</div>
|
||
}
|
||
label="录制完整弹幕(bilibili_danmaku_raw)"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.Input
|
||
field="user.bili_cookie"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
按占位符格式填入 B 站登录 Cookie,推荐使用「
|
||
<a
|
||
href="https://github.com/biliup/biliup-rs"
|
||
title="「biliup-rs」 Github 项目主页"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
style={{ color: 'var(--semi-color-link)' }}
|
||
>
|
||
biliup-rs
|
||
</a>
|
||
」获取。未登录时部分直播间只能录到较低画质。
|
||
</div>
|
||
}
|
||
placeholder="SESSDATA=none;bili_jct=none;DedeUserID__ckMd5=none;DedeUserID=none;"
|
||
label="哔哩哔哩 Cookie 文本(bili_cookie)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.Select
|
||
field="user.bili_cookie_file"
|
||
label="哔哩哔哩 Cookie 文件(bili_cookie_file)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
optionList={list}
|
||
extraText="从已登录的 B 站账号中选择,只支持「biliup-rs」生成的 Cookie 文件。与上方「Cookie 文本」同时填写时,以 Cookie 文本为准。"
|
||
showClear={true}
|
||
/>
|
||
<Form.Select
|
||
field="bili_protocol"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
直播流协议,默认 stream(FLV)。
|
||
<br />
|
||
选 hls_fmp4 时,开播后会先等待下方「hls_fmp4 转码等待时间」,仍拿不到 fmp4 流则本场回退为
|
||
FLV。
|
||
<br />
|
||
默认的下载插件 mesio 支持 hls_fmp4(保存为 .mp4);<strong>stream-gears 不支持 hls_fmp4</strong>,选了
|
||
stream-gears 的需改为 mesio、ffmpeg 或 streamlink。
|
||
</div>
|
||
}
|
||
label="直播流协议(bili_protocol)"
|
||
placeholder="stream(flv,默认)"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
>
|
||
<Select.Option value="stream">stream(flv,默认)</Select.Option>
|
||
<Select.Option value="hls_fmp4">hls_fmp4</Select.Option>
|
||
</Form.Select>
|
||
<Form.Input
|
||
field="bili_liveapi"
|
||
extraText="获取直播流地址时优先使用的 API,默认官方 API。填入反代地址可获取指定区域(大陆或海外)的直播流。"
|
||
label="哔哩哔哩直播主要API(bili_liveapi)"
|
||
style={{ width: '100%' }}
|
||
placeholder="https://api.live.bilibili.com"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
rules={[
|
||
{
|
||
pattern:
|
||
/^https?:\/\/(?:[\w-]+(?::[\w-]+)?@)?([\w-]+\.)+[\w-]+(?::\d+)?(?:\/[\w-/.]*)?$/,
|
||
message: '请输入有效的API地址,必须以 http:// 或 https:// 开头',
|
||
},
|
||
]}
|
||
/>
|
||
<Form.Input
|
||
field="bili_fallback_api"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
上方主要 API 不可用、受区域限制或没有所选协议的流时,改用此 API 重新获取。默认官方 API。
|
||
<br />
|
||
<strong>海外机器玩法</strong>:主要 API 填能取到大陆直播流的反代,回退 API 保持官方;直播流协议选
|
||
hls_fmp4,下载插件选 streamlink,直播 CDN 填 cn-gotcha204,ov-gotcha05。大主播即可走 cn204 的
|
||
fmp4 流稳定录制,没有 fmp4 流的小主播自动回退到 ov05 的 FLV 流。
|
||
</div>
|
||
}
|
||
label="哔哩哔哩直播回退API(bili_fallback_api)"
|
||
style={{ width: '100%' }}
|
||
placeholder="https://api.live.bilibili.com"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
rules={[
|
||
{
|
||
pattern:
|
||
/^https?:\/\/(?:[\w-]+(?::[\w-]+)?@)?([\w-]+\.)+[\w-]+(?::\d+)?(?:\/[\w-/.]*)?$/,
|
||
message: '请输入有效的API地址,必须以 http:// 或 https:// 开头',
|
||
},
|
||
]}
|
||
/>
|
||
<Form.TagInput
|
||
field="bili_cdn"
|
||
extraText="优先使用的 CDN 节点,默认不指定(使用 B 站返回的第一个节点)。可填多个,按先后顺序匹配;都匹配不上时仍用第一个节点。"
|
||
label="直播CDN(bili_cdn)"
|
||
placeholder="例: cn-gotcha204,ov-gotcha05。用英文逗号分隔以批量输入,失焦/Enter保存"
|
||
style={{ width: '100%' }}
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
rules={[
|
||
{
|
||
validator: (rule, value) => {
|
||
value = value ?? []
|
||
return Array.isArray(value) && value.every(item => /^(cn|ov)-gotcha\d+$/.test(item))
|
||
},
|
||
message: '例: cn-gotcha204,ov-gotcha05',
|
||
},
|
||
]}
|
||
/>
|
||
<Form.Switch
|
||
field="bili_cdn_fallback"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
默认关闭。开启后会先探测选中的流地址能否下载,不可用时自动改用同一协议、同一画质下的其他 CDN
|
||
节点。
|
||
<br />
|
||
例:海外机器优选 ov-gotcha05,但该节点一直拉不到流,会自动回退到 ov-gotcha07。
|
||
</div>
|
||
}
|
||
label="CDN 回退(bili_cdn_fallback)"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.Switch
|
||
field="bili_anonymous_origin"
|
||
extraText={
|
||
<div style={{ fontSize: '14px' }}>
|
||
默认关闭。开启后改从 master playlist 接口获取 hls_fmp4 原画流,<strong>不登录也能录到原画</strong>。
|
||
<br />
|
||
仅在直播流协议为 hls_fmp4 时生效;特殊类型直播间(如付费直播)未填 Cookie 时不走此通道,按普通方式取流。
|
||
</div>
|
||
}
|
||
label="免登录原画(bili_anonymous_origin)"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
/>
|
||
<Form.InputNumber
|
||
field="bili_hls_transcode_timeout"
|
||
extraText="直播流协议为 hls_fmp4 时,从 B 站记录的开播时间起最多等待这么多秒让 fmp4 流生成;超时仍没有则本场回退为 FLV 流。单位:秒,默认 60。"
|
||
label="hls_fmp4 转码等待时间(bili_hls_transcode_timeout)"
|
||
style={{ width: '100%' }}
|
||
placeholder="60"
|
||
fieldStyle={{
|
||
alignSelf: 'stretch',
|
||
padding: 0,
|
||
}}
|
||
showClear={true}
|
||
/>
|
||
</PlatformPanel>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default Bilibili
|