feat: 预览播放器手机风格视觉改造 — 9:16竖屏外框+毛玻璃控制条 #1494
@@ -277,20 +277,29 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
if (!ready || !assets.length) {
|
||||
return (
|
||||
<div
|
||||
className="xx-preview-empty"
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
maxWidth: 360,
|
||||
aspectRatio: "9 / 16",
|
||||
background: "#0a0a0a",
|
||||
borderRadius: 28,
|
||||
overflow: "hidden",
|
||||
boxShadow:
|
||||
"0 4px 6px -1px rgba(0,0,0,0.3), 0 20px 50px -12px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.06)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<SoundOutlined style={{ fontSize: 48, color: "var(--text-tertiary)", marginBottom: 12 }} />
|
||||
<p className="xx-preview-empty-title">准备预览素材...</p>
|
||||
<p className="xx-preview-empty-desc">加载素材后即可预览播放</p>
|
||||
<SoundOutlined style={{ fontSize: 40, color: "rgba(255,255,255,0.3)", marginBottom: 12 }} />
|
||||
<p style={{ color: "rgba(255,255,255,0.6)", fontSize: 14, margin: "0 0 4px" }}>
|
||||
准备预览素材...
|
||||
</p>
|
||||
<p style={{ color: "rgba(255,255,255,0.35)", fontSize: 12, margin: 0 }}>
|
||||
加载素材后即可预览播放
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -300,31 +309,48 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
const showDecodeError = forceVideoFallback && canvasState.hasDecodeError
|
||||
return (
|
||||
<div
|
||||
className="xx-preview-empty"
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
maxWidth: 360,
|
||||
aspectRatio: "9 / 16",
|
||||
background: "#0a0a0a",
|
||||
borderRadius: 28,
|
||||
overflow: "hidden",
|
||||
boxShadow:
|
||||
"0 4px 6px -1px rgba(0,0,0,0.3), 0 20px 50px -12px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.06)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 1,
|
||||
padding: 24,
|
||||
}}
|
||||
>
|
||||
{isBuffering ? (
|
||||
<>
|
||||
<LoadingOutlined style={{ fontSize: 48, color: "#fff", marginBottom: 12 }} spin />
|
||||
<p style={{ color: "rgba(255,255,255,0.8)" }}>加载中...</p>
|
||||
<LoadingOutlined style={{ fontSize: 40, color: "#fff", marginBottom: 12 }} spin />
|
||||
<p style={{ color: "rgba(255,255,255,0.8)", fontSize: 14, margin: 0 }}>加载中...</p>
|
||||
</>
|
||||
) : showDecodeError ? (
|
||||
<>
|
||||
<PlayCircleOutlined style={{ fontSize: 48, color: "#ef4444", marginBottom: 12 }} />
|
||||
<p className="xx-preview-empty-title" style={{ color: "rgba(255,255,255,0.9)" }}>
|
||||
<PlayCircleOutlined style={{ fontSize: 40, color: "#ef4444", marginBottom: 12 }} />
|
||||
<p
|
||||
style={{
|
||||
color: "rgba(255,255,255,0.9)",
|
||||
fontSize: 14,
|
||||
margin: "0 0 4px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
视频解码失败
|
||||
</p>
|
||||
<p
|
||||
className="xx-preview-empty-desc"
|
||||
style={{ color: "rgba(255,255,255,0.6)", maxWidth: 300, textAlign: "center" }}
|
||||
style={{
|
||||
color: "rgba(255,255,255,0.5)",
|
||||
fontSize: 12,
|
||||
margin: 0,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{canvasState.errorMessage || "当前浏览器不支持该视频编码格式,请刷新重试"}
|
||||
</p>
|
||||
@@ -332,10 +358,14 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
) : (
|
||||
<>
|
||||
<PlayCircleOutlined
|
||||
style={{ fontSize: 48, color: "var(--text-tertiary)", marginBottom: 12 }}
|
||||
style={{ fontSize: 40, color: "rgba(255,255,255,0.3)", marginBottom: 12 }}
|
||||
/>
|
||||
<p className="xx-preview-empty-title">暂无可播放素材</p>
|
||||
<p className="xx-preview-empty-desc">请先在左侧选择素材</p>
|
||||
<p style={{ color: "rgba(255,255,255,0.6)", fontSize: 14, margin: "0 0 4px" }}>
|
||||
暂无可播放素材
|
||||
</p>
|
||||
<p style={{ color: "rgba(255,255,255,0.35)", fontSize: 12, margin: 0 }}>
|
||||
请先在左侧选择素材
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -347,11 +377,13 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: 400,
|
||||
background: "#000",
|
||||
borderRadius: 8,
|
||||
maxWidth: 360,
|
||||
aspectRatio: "9 / 16",
|
||||
background: "#0a0a0a",
|
||||
borderRadius: 28,
|
||||
overflow: "hidden",
|
||||
boxShadow:
|
||||
"0 4px 6px -1px rgba(0,0,0,0.3), 0 20px 50px -12px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.06)",
|
||||
}}
|
||||
>
|
||||
{/* ── Canvas 渲染层(WebCodecs 路径) ── */}
|
||||
@@ -447,54 +479,69 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 播放按钮 */}
|
||||
{/* 中央播放按钮 */}
|
||||
{!isPlaying && (
|
||||
<button
|
||||
className="xx-preview-play-btn"
|
||||
onClick={handleTogglePlay}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
background: "rgba(0,0,0,0.5)",
|
||||
border: "none",
|
||||
background: "rgba(0,0,0,0.45)",
|
||||
backdropFilter: "blur(12px)",
|
||||
WebkitBackdropFilter: "blur(12px)",
|
||||
border: "1px solid rgba(255,255,255,0.15)",
|
||||
borderRadius: "50%",
|
||||
width: 56,
|
||||
height: 56,
|
||||
width: 72,
|
||||
height: 72,
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "#fff",
|
||||
fontSize: 28,
|
||||
fontSize: 36,
|
||||
zIndex: 10,
|
||||
transition: "transform 0.2s ease, background 0.2s ease",
|
||||
boxShadow: "0 4px 20px rgba(0,0,0,0.4)",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = "translate(-50%, -50%) scale(1.08)"
|
||||
e.currentTarget.style.background = "rgba(0,0,0,0.6)"
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "translate(-50%, -50%) scale(1)"
|
||||
e.currentTarget.style.background = "rgba(0,0,0,0.45)"
|
||||
}}
|
||||
>
|
||||
<PlayCircleOutlined />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 片段指示器 */}
|
||||
{/* 片段指示器 — 右上角胶囊 */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
left: 8,
|
||||
background: "rgba(0,0,0,0.6)",
|
||||
color: "#fff",
|
||||
fontSize: 11,
|
||||
padding: "2px 8px",
|
||||
borderRadius: 4,
|
||||
top: 14,
|
||||
right: 14,
|
||||
background: "rgba(0,0,0,0.45)",
|
||||
backdropFilter: "blur(8px)",
|
||||
WebkitBackdropFilter: "blur(8px)",
|
||||
color: "rgba(255,255,255,0.9)",
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
padding: "4px 12px",
|
||||
borderRadius: 999,
|
||||
zIndex: 10,
|
||||
border: "1px solid rgba(255,255,255,0.1)",
|
||||
letterSpacing: 0.3,
|
||||
}}
|
||||
>
|
||||
{`片段 ${videoCurrentSegIdx + 1}/${segments.length}`}
|
||||
{`${videoCurrentSegIdx + 1} / ${segments.length}`}
|
||||
</div>
|
||||
|
||||
{/* 控制条 */}
|
||||
{/* 控制条 — 手机风格毛玻璃 */}
|
||||
<div
|
||||
className="xx-preview-controls"
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
@@ -502,23 +549,36 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
right: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
padding: "8px 12px",
|
||||
background: "linear-gradient(transparent, rgba(0,0,0,0.6))",
|
||||
gap: 10,
|
||||
padding: "12px 16px 16px",
|
||||
background: "linear-gradient(transparent, rgba(0,0,0,0.7))",
|
||||
backdropFilter: "blur(4px)",
|
||||
WebkitBackdropFilter: "blur(4px)",
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={handleTogglePlay}
|
||||
style={{
|
||||
background: "none",
|
||||
background: "rgba(255,255,255,0.15)",
|
||||
border: "none",
|
||||
color: "#fff",
|
||||
fontSize: 18,
|
||||
fontSize: 16,
|
||||
cursor: "pointer",
|
||||
padding: 4,
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: "50%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
transition: "background 0.15s",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = "rgba(255,255,255,0.25)"
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = "rgba(255,255,255,0.15)"
|
||||
}}
|
||||
>
|
||||
{isPlaying ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
|
||||
@@ -526,10 +586,11 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: "rgba(255,255,255,0.8)",
|
||||
minWidth: 80,
|
||||
fontSize: 11,
|
||||
color: "rgba(255,255,255,0.85)",
|
||||
minWidth: 72,
|
||||
fontVariantNumeric: "tabular-nums",
|
||||
letterSpacing: 0.2,
|
||||
}}
|
||||
>
|
||||
{formatTime(currentTime)} / {formatTime(totalDuration)}
|
||||
@@ -540,8 +601,8 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
onMouseDown={handleMouseDown}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 4,
|
||||
background: "rgba(255,255,255,0.15)",
|
||||
height: 3,
|
||||
background: "rgba(255,255,255,0.2)",
|
||||
borderRadius: 2,
|
||||
cursor: "pointer",
|
||||
position: "relative",
|
||||
@@ -551,7 +612,7 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
style={{
|
||||
height: "100%",
|
||||
width: `${progressPercent}%`,
|
||||
background: "#3b82f6",
|
||||
background: "#fff",
|
||||
borderRadius: 2,
|
||||
transition: isDragging ? "none" : "width 0.1s linear",
|
||||
}}
|
||||
@@ -565,8 +626,8 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: "50%",
|
||||
background: "#3b82f6",
|
||||
border: "2px solid #fff",
|
||||
background: "#fff",
|
||||
boxShadow: "0 0 6px rgba(255,255,255,0.5)",
|
||||
opacity: isDragging ? 1 : 0,
|
||||
transition: "opacity 0.15s",
|
||||
}}
|
||||
|
||||
@@ -108,10 +108,9 @@
|
||||
============================================================ */
|
||||
.xx-generate-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 320px;
|
||||
grid-template-columns: 1fr 400px;
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -2526,9 +2525,8 @@
|
||||
.xx-generate-right-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.xx-preview-header {
|
||||
|
||||
Reference in New Issue
Block a user