mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
优化视频头尾跳过功能
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { api } from './client'
|
||||
import type { PlayProfile } from '../types'
|
||||
import type { PlayProfile, SegmentSource } from '../types'
|
||||
|
||||
// Payload accepted by create / update.
|
||||
export interface PlayProfileInput {
|
||||
@@ -14,6 +14,7 @@ export interface PlayProfileInput {
|
||||
preferred_audio_lang?: string
|
||||
autoplay_next: boolean
|
||||
skip_intro: boolean
|
||||
segment_source: SegmentSource
|
||||
allowed_library_ids: string[]
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,13 @@ type PlaybackProgressSession = {
|
||||
// 自动跳过片头后,「已跳过 · 撤销」提示停留的时长。
|
||||
const SKIP_NOTICE_MS = 6000
|
||||
|
||||
// ffprobe 章节提取在服务端是异步的:pending 为 true 时按这个间隔重试,最多这么
|
||||
// 多次(远端探测实测 3~4 秒,本地更快;40 秒的预算足以覆盖慢速 CDN 与大文件)。
|
||||
// 重试期间播放完全不受影响;拿到数据时如果片头已经播过去了,resolveActiveSkip
|
||||
// 自然不会再提示,不会出现「点一下就跳过头」的按钮。
|
||||
const SKIP_SEGMENTS_POLL_MS = 4000
|
||||
const SKIP_SEGMENTS_MAX_POLLS = 10
|
||||
|
||||
function normalizePlayerVolume(value: unknown): number {
|
||||
const parsed = Number(value)
|
||||
if (!Number.isFinite(parsed)) return 1
|
||||
@@ -732,6 +739,7 @@ export function PlayerPage() {
|
||||
useEffect(() => {
|
||||
if (!mediaId) return
|
||||
let cancelled = false
|
||||
let pollTimer: ReturnType<typeof setTimeout> | null = null
|
||||
setRawSkipSegments([])
|
||||
setAutoSkipIntro(false)
|
||||
setActiveSkip(null)
|
||||
@@ -739,16 +747,28 @@ export function PlayerPage() {
|
||||
setDismissedSkipKinds([])
|
||||
setAutoSuppressedKinds([])
|
||||
// 片段数据与播放来源无关,播放开始后异步补抓即可,绝不挡在起播路径上。
|
||||
playbackAPI
|
||||
.segments(mediaId)
|
||||
.then((res) => {
|
||||
if (cancelled) return
|
||||
setAutoSkipIntro(Boolean(res.auto_skip))
|
||||
setRawSkipSegments(res.segments ?? [])
|
||||
})
|
||||
.catch(() => undefined)
|
||||
//
|
||||
// 服务端的 ffprobe 章节提取是异步的:pending 为 true 说明这次还没结果,隔几秒
|
||||
// 再拉一次。提取完成后如果片头还没播完,跳过按钮会自己出现;已经过了片头时间
|
||||
// 的话 resolveActiveSkip 不会提示,所以不会出现「点一下就跳过头」的按钮。
|
||||
const load = (attempt: number) => {
|
||||
playbackAPI
|
||||
.segments(mediaId)
|
||||
.then((res) => {
|
||||
if (cancelled) return
|
||||
setAutoSkipIntro(Boolean(res.auto_skip))
|
||||
setRawSkipSegments(res.segments ?? [])
|
||||
const hasSegments = (res.segments ?? []).length > 0
|
||||
if (res.pending && !hasSegments && attempt < SKIP_SEGMENTS_MAX_POLLS) {
|
||||
pollTimer = setTimeout(() => load(attempt + 1), SKIP_SEGMENTS_POLL_MS)
|
||||
}
|
||||
})
|
||||
.catch(() => undefined)
|
||||
}
|
||||
load(0)
|
||||
return () => {
|
||||
cancelled = true
|
||||
if (pollTimer) clearTimeout(pollTimer)
|
||||
}
|
||||
}, [mediaId])
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export function ProfileFormModal({
|
||||
preferred_audio_lang: editing?.preferred_audio_lang ?? '',
|
||||
autoplay_next: editing?.autoplay_next ?? true,
|
||||
skip_intro: editing?.skip_intro ?? false,
|
||||
segment_source: editing?.segment_source ?? 'auto',
|
||||
allowed_library_ids: editing?.allowed_library_ids ?? [],
|
||||
}))
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
@@ -126,6 +126,17 @@ export function ProfilePreferenceFields({
|
||||
checked={form.skip_intro}
|
||||
onChange={(value) => update({ skip_intro: value })}
|
||||
/>
|
||||
<Field label="片头片尾数据来源">
|
||||
<select
|
||||
className="input-base"
|
||||
value={form.segment_source}
|
||||
onChange={(event) => update({ segment_source: event.target.value as PlayProfileInput['segment_source'] })}
|
||||
>
|
||||
<option value="auto">自动(优先用文件内嵌章节,没有可用章节时用 TheIntroDB)</option>
|
||||
<option value="theintrodb">TheIntroDB 社区数据库(依赖刮削出的 TMDb ID)</option>
|
||||
<option value="ffprobe">ffprobe 本地提取(读取文件内嵌章节,不联网)</option>
|
||||
</select>
|
||||
</Field>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,9 +10,13 @@ export interface PlayProfile {
|
||||
preferred_audio_lang?: string
|
||||
autoplay_next: boolean
|
||||
skip_intro: boolean
|
||||
segment_source?: SegmentSource
|
||||
allowed_library_ids: string[]
|
||||
total_watch_time: number
|
||||
last_active_at?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
/** 片头/片尾数据来源:auto 优先文件内嵌章节,没有则回落 TheIntroDB。 */
|
||||
export type SegmentSource = 'auto' | 'theintrodb' | 'ffprobe'
|
||||
|
||||
@@ -43,5 +43,16 @@ export interface PlaybackSegmentsResponse {
|
||||
segments: PlaybackSegment[]
|
||||
/** 当前生效播放档案的「自动跳过片头」开关。 */
|
||||
auto_skip: boolean
|
||||
/**
|
||||
* true 表示 ffprobe 章节提取还在服务端后台跑:这次可能还没有章节数据,隔几秒
|
||||
* 再拉一次就能拿到。拿到时若片头还没播完,跳过按钮会自动出现;已经过了片头
|
||||
* 时间则不会提示。
|
||||
*/
|
||||
pending?: boolean
|
||||
/** 本次实际生效的数据来源。 */
|
||||
source?: PlaybackSegmentSource
|
||||
}
|
||||
|
||||
/** 片头/片尾数据来源,与后端 play_profiles.segment_source 一致。 */
|
||||
export type PlaybackSegmentSource = 'auto' | 'theintrodb' | 'ffprobe'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user