* 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
86 lines
3.0 KiB
TypeScript
86 lines
3.0 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 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
|