Files
biliup/app/ui/plugins/twitch.tsx
T
Noah 67e49354ad fix(recorder): stop stream-gears losing the tail, default to mesio (#1733)
* 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
2026-09-24 06:24:17 +00:00

86 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 Twitch: 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="Twitch" itemKey="twitch" bare={bare}>
<Form.Switch
field="twitch_danmaku"
extraText="录制 Twitch 弹幕,默认关闭"
label="录制弹幕(twitch_danmaku)"
/>
<Form.Switch
initValue={
entity?.hasOwnProperty('twitch_disable_ads') ? entity['twitch_disable_ads'] : true
}
field="twitch_disable_ads"
extraText={
<div style={{ fontSize: '14px' }}>
默认开启。去广告的原理是遇到广告就断开重连,因此<strong>录像会在每次广告处分段</strong>。
<br />
关闭后录像不再因广告分段,但广告时段会录成紫色的「Commercial Time」画面。
<br />
更好的办法是开通 Twitch Turbo 会员并在下方填入 Twitch Cookie,可直接免广告。
<br />
<strong>仅下载插件为 streamlink 或 ffmpeg 时生效</strong>;默认的 mesio 和 stream-gears 直接拉流,此开关不起作用。
</div>
}
label="去除广告(twitch_disable_ads)"
fieldStyle={{
alignSelf: 'stretch',
padding: 0,
}}
/>
<Form.Input
field="user.twitch_cookie"
extraText={
<div style={{ fontSize: '14px' }}>
<strong>仅限 Twitch Turbo 会员</strong>:填入后可大幅减少录像中的广告。
<br />
此处填的是 auth-token 的值,不是整段 Cookie。获取方式:浏览器打开 twitch.tv,按 F12
打开控制台,执行:
<br />
<code style={{ color: 'var(--semi-color-primary)' }}>
{`document.cookie.split("; ").find(item => item.startsWith("auth-token="))?.split("=")[1]`}
</code>
<br />
该值会过期(作者实测可用四个月以上);失效后日志会输出警告并忽略它继续录制,请及时更换。
<br />
<strong>仅下载插件为 streamlink 或 ffmpeg 时生效</strong>。
</div>
}
label="Twitch Cookie(twitch_cookie)"
style={{ width: '100%' }}
fieldStyle={{
alignSelf: 'stretch',
padding: 0,
}}
/>
</PlatformPanel>
</>
)
}
export default Twitch