feat: 支持长按右键/D键二倍速播放与视频AB循环

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-04 02:00:05 +08:00
co-authored by Cursor
parent bcf1e6e349
commit 11d9d35002
8 changed files with 431 additions and 37 deletions
@@ -17,7 +17,7 @@ import {
} from "lucide-react";
import Link from "next/link";
import { memo, type ReactNode, type RefObject } from "react";
import type { LoopMode, ObjectFit, User } from "../types.ts";
import type { AbLoop, LoopMode, ObjectFit, User } from "../types.ts";
import { formatAbsoluteUTC, formatRelativeTime, formatTime } from "../utils";
import { MoreMenu, MoreMenuItem } from "./MoreMenu";
import { ProgressBar } from "./ProgressBar";
@@ -38,6 +38,8 @@ interface MediaControlsProps {
desc: string;
// 视频专用
videoRef?: RefObject<HTMLVideoElement | null>;
abLoop?: AbLoop;
onClearAbLoop?: () => void;
// 图文专用
currentIndex?: number;
totalSegments?: number;
@@ -76,6 +78,8 @@ export const MediaControls = memo(function MediaControls({
createdAt,
desc,
videoRef,
abLoop,
onClearAbLoop,
currentIndex = 0,
totalSegments = 0,
segmentProgress = 0,
@@ -182,7 +186,12 @@ export const MediaControls = memo(function MediaControls({
onSeek={onSeek}
/>
) : (
<ProgressBar progress={progress} onSeek={onSeek} />
<ProgressBar
progress={progress}
onSeek={onSeek}
abLoop={isVideo ? abLoop : undefined}
duration={isVideo ? (videoRef?.current?.duration ?? 0) : 0}
/>
)}
{/* 控制按钮行 - 响应式布局 */}
@@ -228,6 +237,27 @@ export const MediaControls = memo(function MediaControls({
{rate}x
</button>
)}
{/* AB 循环状态提示与快捷清除 */}
{isVideo && abLoop && (abLoop.a !== null || abLoop.b !== null) && (
<button
type="button"
onClick={onClearAbLoop}
className="h-[30px] px-2.5 rounded-full text-[11px] font-mono bg-amber-400/20 text-amber-300 border border-amber-400/50 cursor-pointer shrink-0 hover:bg-amber-400/30 transition-colors inline-flex items-center gap-1"
title="点击清除 AB 循环 (快捷键 \)"
aria-label="清除 AB 循环"
>
<span>
AB {formatTime(abLoop.a ?? 0)}-
{abLoop.b !== null
? formatTime(abLoop.b)
: videoRef?.current?.duration
? formatTime(videoRef.current.duration)
: "末"}
</span>
<span className="text-amber-200/80 text-xs leading-none">×</span>
</button>
)}
</div>
{/* 中间:音量控制 - 中等屏幕以上显示 */}