refactor: 重构作品播放页性能、媒体同步与内存管理

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-04 01:31:10 +08:00
co-authored by Cursor
parent 990e1b1804
commit 10f2520279
19 changed files with 1351 additions and 813 deletions
@@ -1,7 +1,7 @@
import Link from "next/link";
import {
ArrowDownUp,
Download,
FileText,
Maximize,
Maximize2,
Minimize,
@@ -14,14 +14,14 @@ import {
RotateCw,
Volume2,
VolumeX,
FileText,
} from "lucide-react";
import type { RefObject } from "react";
import Link from "next/link";
import { memo, type ReactNode, type RefObject } from "react";
import type { LoopMode, ObjectFit, User } from "../types.ts";
import { formatRelativeTime, formatTime, formatAbsoluteUTC } from "../utils";
import { formatAbsoluteUTC, formatRelativeTime, formatTime } from "../utils";
import { MoreMenu, MoreMenuItem } from "./MoreMenu";
import { ProgressBar } from "./ProgressBar";
import { SegmentedProgressBar } from "./SegmentedProgressBar";
import { MoreMenu, MoreMenuItem } from "./MoreMenu";
interface MediaControlsProps {
isVideo: boolean;
@@ -59,7 +59,10 @@ interface MediaControlsProps {
onToggleFullscreen: () => void;
}
export function MediaControls({
const CONTROL_BTN_CLASS =
"w-[34px] h-[34px] inline-flex items-center justify-center rounded-full bg-white/15 text-white border border-white/20 cursor-pointer hover:bg-white/25 transition-colors";
export const MediaControls = memo(function MediaControls({
isVideo,
isPlaying,
progress,
@@ -101,7 +104,7 @@ export function MediaControls({
: loopMode === "loop"
? "图文循环"
: "顺序播放";
const handleLoopModeToggle = () => {
const handleLoopModeToggle = (): void => {
if (isVideo) {
onLoopModeChange(normalizedLoopMode === "loop" ? "sequential" : "loop");
return;
@@ -115,7 +118,7 @@ export function MediaControls({
: "loop",
);
};
const renderLoopIcon = () => {
const renderLoopIcon = (): ReactNode => {
if (!isVideo && loopMode === "single") return <Repeat1 size={18} />;
if (normalizedLoopMode === "loop") return <Repeat size={18} />;
return <ArrowDownUp size={18} />;
@@ -131,7 +134,7 @@ export function MediaControls({
className="flex items-center gap-2.5 pointer-events-auto hover:opacity-80 transition-opacity"
>
<img
src={author.avatar_url!}
src={author.avatar_url ?? ""}
alt=""
className="w-8 h-8 rounded-full"
/>
@@ -142,7 +145,7 @@ export function MediaControls({
) : (
<div className="flex items-center gap-2.5">
<img
src={author.avatar_url!}
src={author.avatar_url ?? ""}
alt=""
className="w-8 h-8 rounded-full"
/>
@@ -187,7 +190,8 @@ export function MediaControls({
{/* 左侧:播放控制 + 时间/进度 */}
<div className="inline-flex items-center gap-1.5 sm:gap-2 min-w-0">
<button
className="w-[34px] h-[34px] shrink-0 inline-flex items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`${CONTROL_BTN_CLASS} shrink-0`}
onClick={onTogglePlay}
aria-label={isPlaying ? "暂停" : "播放"}
>
@@ -211,7 +215,8 @@ export function MediaControls({
{/* 倍速 - 中等屏幕以上显示,仅视频 */}
{isVideo && (
<button
className="hidden md:block h-[30px] px-[10px] rounded-full text-[12px] bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer shrink-0"
type="button"
className="hidden md:block h-[30px] px-[10px] rounded-full text-[12px] bg-white/15 text-white border border-white/20 cursor-pointer shrink-0 hover:bg-white/25 transition-colors"
onClick={() => {
const steps = [1, 1.25, 1.5, 2, 0.75, 0.5];
const i = steps.indexOf(rate);
@@ -229,7 +234,8 @@ export function MediaControls({
<div className="hidden md:inline-flex items-center gap-2 shrink-0">
{/* 旋转按钮 - 小屏幕以上显示 */}
<button
className="hidden sm:inline-flex w-[34px] h-[34px] items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`hidden sm:inline-flex ${CONTROL_BTN_CLASS}`}
onClick={() => onRotationChange((rotation + 270) % 360)}
aria-label="向左旋转 90 度"
title="向左旋转 90 度"
@@ -237,7 +243,8 @@ export function MediaControls({
<RotateCcw size={18} />
</button>
<button
className="w-[34px] h-[34px] inline-flex items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={CONTROL_BTN_CLASS}
onClick={() => onVolumeChange(volume > 0 ? 0 : 1)}
aria-label={volume > 0 ? "静音" : "取消静音"}
>
@@ -254,7 +261,8 @@ export function MediaControls({
aria-label="音量"
/>
<button
className="hidden sm:inline-flex w-[34px] h-[34px] items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`hidden sm:inline-flex ${CONTROL_BTN_CLASS}`}
onClick={() => onRotationChange((rotation + 90) % 360)}
aria-label="向右旋转 90 度"
title="向右旋转 90 度"
@@ -267,7 +275,8 @@ export function MediaControls({
<div className="inline-flex items-center gap-1 sm:gap-1.5 lg:gap-2 shrink-0">
{/* 音量按钮 - 仅在小屏幕显示(中等屏幕以上有滑块) */}
<button
className="md:hidden w-[34px] h-[34px] inline-flex items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`md:hidden ${CONTROL_BTN_CLASS}`}
onClick={() => onVolumeChange(volume > 0 ? 0 : 1)}
aria-label={volume > 0 ? "静音" : "取消静音"}
>
@@ -276,7 +285,8 @@ export function MediaControls({
{/* 循环模式 - 中等屏幕以上显示 */}
<button
className="hidden md:inline-flex w-[34px] h-[34px] items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`hidden md:inline-flex ${CONTROL_BTN_CLASS}`}
onClick={handleLoopModeToggle}
aria-label={loopLabel}
title={loopLabel}
@@ -286,7 +296,8 @@ export function MediaControls({
{/* 适配模式 - 小屏幕以上显示 */}
<button
className="hidden sm:inline-flex w-[34px] h-[34px] items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`hidden sm:inline-flex ${CONTROL_BTN_CLASS}`}
onClick={() =>
onObjectFitChange(objectFit === "contain" ? "cover" : "contain")
}
@@ -307,7 +318,8 @@ export function MediaControls({
{/* 转录文本 - 仅视频且有转录时显示,中等屏幕以上 */}
{isVideo && hasTranscript && onShowTranscript && (
<button
className="hidden md:inline-flex w-[34px] h-[34px] items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`hidden md:inline-flex ${CONTROL_BTN_CLASS}`}
onClick={onShowTranscript}
aria-label="显示转录文本"
title="显示转录文本"
@@ -318,7 +330,8 @@ export function MediaControls({
{/* 下载 - 中等屏幕以上显示 */}
<button
className="hidden md:inline-flex w-[34px] h-[34px] items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={`hidden md:inline-flex ${CONTROL_BTN_CLASS}`}
onClick={onDownload}
aria-label={isVideo ? "下载视频" : "下载当前图片"}
title={isVideo ? "下载视频" : "下载当前图片"}
@@ -327,10 +340,8 @@ export function MediaControls({
</button>
{/* 更多菜单 - 只在有按钮被折叠时显示 */}
{/* sm屏幕以下会隐藏适配模式,md屏幕以下会隐藏循环、转录、下载、倍速 */}
<div className="sm:block md:hidden">
<MoreMenu>
{/* 小屏幕隐藏的适配模式 */}
<div className="sm:hidden">
<MoreMenuItem
icon={
@@ -349,7 +360,6 @@ export function MediaControls({
/>
</div>
{/* 中等屏幕以下隐藏的循环模式 */}
<div className="md:hidden">
<MoreMenuItem
icon={renderLoopIcon()}
@@ -358,7 +368,6 @@ export function MediaControls({
/>
</div>
{/* 中等屏幕以下隐藏的转录按钮 */}
{isVideo && hasTranscript && onShowTranscript && (
<div className="md:hidden">
<MoreMenuItem
@@ -369,7 +378,6 @@ export function MediaControls({
</div>
)}
{/* 中等屏幕以下隐藏的下载 */}
<div className="md:hidden">
<MoreMenuItem
icon={<Download size={18} />}
@@ -378,7 +386,6 @@ export function MediaControls({
/>
</div>
{/* 仅在视频模式下显示的倍速(中等屏幕以下) */}
{isVideo && (
<div className="md:hidden">
<MoreMenuItem
@@ -398,7 +405,8 @@ export function MediaControls({
{/* 全屏 - 所有设备都显示 */}
<button
className="w-[34px] h-[34px] inline-flex items-center justify-center rounded-full bg-white/15 text-white border border-white/20 backdrop-blur-sm cursor-pointer"
type="button"
className={CONTROL_BTN_CLASS}
onClick={onToggleFullscreen}
aria-label="切换全屏"
>
@@ -413,4 +421,4 @@ export function MediaControls({
) : null}
</div>
);
}
});