import type { PlaybackSegment, PlaybackSegmentKind } from '../types/playback' /** 落在源时间轴上的可跳过区间(秒)。 */ export interface SkipSegment { kind: PlaybackSegmentKind startSec: number endSec: number } /** 当前应当在浮层上提示的跳过动作。 */ export interface SkipPrompt extends SkipSegment { label: string } /** 片头/回顾统一叫「跳过片头」;片尾在有下一集时直接叫「下一集」。 */ export function skipLabel(kind: PlaybackSegmentKind, hasNextEpisode: boolean): string { if (kind === 'credits' || kind === 'preview') { return hasNextEpisode ? '下一集' : '跳过片尾' } return '跳过片头' } /** * 自动跳过后的提示文案。自动跳转很容易被当成「进度条自己动了」,所以必须告诉 * 用户发生了什么,并给一个撤销入口。 */ export function skippedNoticeText(kind: PlaybackSegmentKind): string { if (kind === 'credits' || kind === 'preview') return '已跳过片尾' if (kind === 'recap') return '已跳过回顾' return '已跳过片头' } /** * 把服务端返回的片段换算成源时间轴上的秒区间。 * * end_ms === 0 是「一直到片尾」的哨兵值,只有片尾类(credits / preview)会这么 * 返回,用媒体总时长补齐。片头 / 回顾出现 0 一律丢弃:那通常意味着数据源根本没 * 给出结束点(TheIntroDB 用 end_ms: 0 表示「这段不存在」),补成总时长会让整集 * 都落在「片头」区间里,一开播就弹跳过按钮。时长未知(0 / 非有限值)同样丢弃, * 否则会算出一个永远命中的区间,浮层上的按钮就再也点不掉了。 */ export function toSkipSegments( segments: PlaybackSegment[] | null | undefined, durationSec: number, ): SkipSegment[] { if (!segments || segments.length === 0) return [] const out: SkipSegment[] = [] for (const segment of segments) { const startSec = Math.max(0, segment.start_ms / 1000) const openEnded = segment.kind === 'credits' || segment.kind === 'preview' if (segment.end_ms > 0) { const endSec = segment.end_ms / 1000 if (!Number.isFinite(endSec) || endSec <= startSec) continue out.push({ kind: segment.kind, startSec, endSec }) continue } if (!openEnded) continue if (!Number.isFinite(durationSec) || durationSec <= startSec) continue out.push({ kind: segment.kind, startSec, endSec: durationSec }) } return out.sort((a, b) => a.startSec - b.startSec) } export interface ResolveActiveSkipOptions { hasNextEpisode?: boolean /** 本次播放内已经跳过 / 被用户撤销过的类型,不再提示。 */ excludedKinds?: PlaybackSegmentKind[] /** 允许提前一点点出现按钮,避免恰好卡在时间轴上时迟到。 */ toleranceSec?: number } /** * 找出当前播放位置命中的可跳过区间。同一位置有多个区间时取最早开始的那个。 * * 注意 currentSec 必须是「源时间轴」上的绝对秒数:HLS 转码时