Compare commits

...

1 Commits

Author SHA1 Message Date
张宏杰 b2d5f1ba69 fix: 修复标题字号(px计算)+配音同步+片段切换可靠性
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m3s
AI Code Review / AI Code Review (pull_request) Failing after 2m10s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m24s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m50s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m58s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m31s
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m22s
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m24s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 2m2s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 4m18s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 10m55s
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 7s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Has been cancelled
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m2s
1. 标题 fontSize 改用 ResizeObserver 获取容器实际高度,px 单位计算
   修复百分比 fontSize 基于 36px 父元素导致字号≈1.4px 不可见的问题
2. 片段切换时同步 audio.currentTime 到视频全局时间
3. switchToSegment readyState 阈值提升到 2 (HAVE_CURRENT_DATA)
2026-08-18 22:44:40 +08:00
3 changed files with 41 additions and 10 deletions
@@ -134,6 +134,14 @@ const FrontendPreviewPlayer: React.FC<FrontendPreviewPlayerProps> = ({
prevIsPlayingRef.current = isPlaying
}, [isPlaying, currentTime])
// 片段切换时同步音频 — 将 audio.currentTime 对齐到视频全局时间
useEffect(() => {
const audio = audioRef.current
if (!audio || !audio.src || !isPlaying) return
// 用视频当前的全局时间对齐音频
audio.currentTime = currentTime
}, [currentSegmentIndex])
// seek 时同步音频
const handleSeekTo = useCallback(
(time: number) => {
@@ -11,7 +11,7 @@
* FrontendPreviewPlayer 的内容通过 absolute 定位填充容器
* TitleOverlay 通过 absolute 定位 + z-index: 30 覆盖在最上层
*/
import React, { useMemo } from "react"
import React, { useMemo, useRef, useState, useEffect } from "react"
import { LoadingOutlined } from "@ant-design/icons"
import type { AssetItem } from "@/api/assets"
import type { EditingTemplate } from "@/api/editing-planner"
@@ -79,12 +79,16 @@ function getPositionStyle(position: string): React.CSSProperties {
* 构建 CSS 标题层的样式
* 所有渲染参数与后端 FFmpeg ASS 字幕一致
*/
function buildTitleStyle(settings: TitleSettings): React.CSSProperties {
const fontSizePercent = (Math.min(settings.size, 36) / ASS_VIDEO_HEIGHT) * 100
function buildTitleStyle(settings: TitleSettings, containerHeight: number): React.CSSProperties {
// 用 px 计算 fontSize,不再依赖父元素 font-size 的百分比
const fontSizePx =
containerHeight > 0
? (Math.min(settings.size, 36) / ASS_VIDEO_HEIGHT) * containerHeight
: (Math.min(settings.size, 36) / ASS_VIDEO_HEIGHT) * 400 // fallback
const base: React.CSSProperties = {
fontFamily: settings.font || "思源黑体",
fontSize: `${fontSizePercent}%`,
fontSize: `${fontSizePx}px`,
color: settings.color || "#ffffff",
fontWeight: settings.bold ? 700 : 400,
fontStyle: settings.italic ? "italic" : "normal",
@@ -113,15 +117,34 @@ function buildTitleStyle(settings: TitleSettings): React.CSSProperties {
* z-index: 20(在视频 z-index:1 和控制条 z-index:10 之上)
*/
const TitleOverlay: React.FC<{ titleSettings: TitleSettings }> = ({ titleSettings }) => {
const containerRef = useRef<HTMLDivElement>(null)
const [containerHeight, setContainerHeight] = useState(400) // fallback
// ResizeObserver 获取容器实际高度
useEffect(() => {
const el = containerRef.current
if (!el) return
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const h = entry.contentRect.height
if (h > 0) setContainerHeight(h)
}
})
ro.observe(el)
// 初始化也读一次
const rect = el.getBoundingClientRect()
if (rect.height > 0) setContainerHeight(rect.height)
return () => ro.disconnect()
}, [])
const positionStyle = useMemo(
() => getPositionStyle(titleSettings.position),
[titleSettings.position],
)
// eslint-disable-next-line react-hooks/exhaustive-deps
const titleStyle = useMemo(
() => buildTitleStyle(titleSettings),
// eslint-disable-next-line react-hooks/exhaustive-deps
() => buildTitleStyle(titleSettings, containerHeight),
[
containerHeight,
titleSettings.font,
titleSettings.size,
titleSettings.color,
@@ -132,11 +155,11 @@ const TitleOverlay: React.FC<{ titleSettings: TitleSettings }> = ({ titleSetting
],
)
// 标题为空时显示占位文本
const displayTitle = titleSettings.title?.trim() || "标题预览"
return (
<div
ref={containerRef}
style={{
position: "absolute",
inset: 0,
@@ -197,9 +197,9 @@ export function useSegmentScheduler(segments: PlaybackSegment[]): SegmentSchedul
video.src = videoUrl
video.load()
// ✅ 关键修复:如果 readyState >= 1(已有 metadata),说明加载已完成
// ✅ 关键修复:如果 readyState >= 2HAVE_CURRENT_DATA),说明已有帧数据
// 直接设置 currentTime 并 resolve,不等待事件
if (video.readyState >= 1) {
if (video.readyState >= 2) {
video.currentTime = localTime
srcLoadedRef.current = true
cleanup()