From ed1d1ac1327b2363bab7029bd311d83232aae2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Tue, 19 Aug 2025 03:20:12 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=9B=B4=E6=8D=A2=20=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E7=94=9F=E8=A7=86=E9=A2=91=20=E8=BD=AE=E8=AF=A2=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/video_flow.ts | 13 ++ app/service/Interaction/ShotService.ts | 206 ++++++++++++++++++------- components/ui/shot-tab-content.tsx | 5 +- 3 files changed, 165 insertions(+), 59 deletions(-) diff --git a/api/video_flow.ts b/api/video_flow.ts index ce3a9fe..02916bc 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -1118,6 +1118,19 @@ export const checkShotVideoStatus = async (request: { return post>("/check_shot_video_status", request); }; +/** + * 检查分镜视频状态 + * @description 保存当前项目数据 + * @param request - 请求参数 + * @returns Promise> 保存结果 + */ +export const getNewShotVideo = async (request: { + /** 项目ID */ + task_id: string; +}): Promise> => { + return post>("/movie/check_shot_video_status", request); +}; + /** * 智能检测修改类型并进行相应的修改操作 * @description 支持角色修改和分镜修改的自动判断与批量处理 diff --git a/app/service/Interaction/ShotService.ts b/app/service/Interaction/ShotService.ts index 1aeb607..598c69f 100644 --- a/app/service/Interaction/ShotService.ts +++ b/app/service/Interaction/ShotService.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback, useEffect, useRef } from "react"; import { VideoSegmentEditUseCase, } from "../usecase/ShotEditUsecase"; @@ -10,6 +10,7 @@ import { ScriptRoleEntity, VideoSegmentEntity } from "../domain/Entities"; import { LensType, SimpleCharacter } from "../domain/valueObject"; import { getUploadToken, uploadToQiniu } from "@/api/common"; import { SaveEditUseCase } from "../usecase/SaveEditUseCase"; +import { getNewShotVideo } from "@/api/video_flow"; /** * 视频片段服务Hook接口 @@ -97,7 +98,6 @@ export const useShotService = (): UseShotService => { setProjectId(projectId); setVideoSegments(segments); setScriptRoles(roles); - setIntervalIdHandler(projectId); } catch (error) { console.error("获取视频片段列表失败:", error); } finally { @@ -107,56 +107,6 @@ export const useShotService = (): UseShotService => { [vidoEditUseCase] ); - const setIntervalIdHandler = async (projectId: string): Promise => { - // 每次执行前先清除之前的定时器,确保只存在一个定时器 - if (intervalId) { - clearInterval(intervalId); - setIntervalId(null); - } - // 定义定时任务,每5秒执行一次 - const newIntervalId = setInterval(async () => { - try { - const { segments } = await vidoEditUseCase.getVideoSegmentList(projectId,()=>{ - if (intervalId) { - clearInterval(intervalId); - setIntervalId(null); - } - }); - - setVideoSegments((prevSegments) => { - const existingSegmentsMap = new Map( - prevSegments.map((segment) => [segment.id, segment]) - ); - const segmentsToUpdate = segments.filter( - (segment) => segment.id !== selectedSegment?.id - ); - - segmentsToUpdate.forEach((newSegment) => { - const existingSegment = existingSegmentsMap.get(newSegment.id); - - if (existingSegment) { - existingSegmentsMap.set(newSegment.id, { - ...existingSegment, - videoUrl: newSegment.videoUrl, - status: newSegment.status, - sketchUrl: newSegment.sketchUrl, - lens: newSegment.lens, - }); - } else { - existingSegmentsMap.set(newSegment.id, newSegment); - } - }); - - return Array.from(existingSegmentsMap.values()); - }); - } catch (error) { - console.error("定时获取视频片段列表失败:", error); - } - }, 30000); - - setIntervalId(newIntervalId); - }; - // 组件卸载时清理定时器 useEffect(() => { return () => { @@ -167,6 +117,145 @@ export const useShotService = (): UseShotService => { }; }, [intervalId]); + /** + * 轮询获取视频生成状态 + * @param taskId - 任务ID + * @returns Promise + */ + // 使用 ref 来跟踪组件是否已卸载 + const isComponentMounted = useRef(true); + + // 在组件挂载时设置为 true,卸载时设置为 false + useEffect(() => { + isComponentMounted.current = true; + + return () => { + isComponentMounted.current = false; + }; + }, []); + + const pollVideoStatus = useCallback(async (taskId: string): Promise => { + const maxAttempts = 60; // 最大轮询次数 + const interval = 10000; // 轮询间隔时间(毫秒) + let attempts = 0; + let timeoutId: NodeJS.Timeout; + + const poll = async (): Promise => { + + // 如果组件已卸载,停止轮询 + if (!isComponentMounted.current) { + if (timeoutId) { + clearTimeout(timeoutId); + } + return; + } + try { + const result = await getNewShotVideo({ task_id: taskId }); + + if (!result?.data) { + // 更新 selectedSegment + setSelectedSegment(prev => ({ + ...prev!, + videoUrl: [], + video_status: 2 // 设置为失败状态 + })); + + // 同步更新 videoSegments 中的对应项 + setVideoSegments(prev => + prev.map(segment => + segment.id === selectedSegment!.id + ? { + ...segment, + videoUrl: [], + video_status: 2, + status: 2 // 更新片段状态为失败 + } + : segment + ) + ); + } + + const { status, urls, message } = result.data; + + // 如果任务完成或失败,更新视频片段数据 + if (status === 'COMPLETED' || status === 'FAILED') { + if (selectedSegment && status === 'COMPLETED' && urls?.length > 0) { + // 更新 selectedSegment + const updatedVideos = urls.map((url: string) => ({ + video_url: url, + video_id: selectedSegment!.id, + video_status: 1 + })); + + + setSelectedSegment(prev => ({ + ...prev!, + videoUrl: updatedVideos, + video_status: 1 // 设置为已完成状态 + })); + + // 同步更新 videoSegments 中的对应项 + setVideoSegments(prev => + prev.map(segment => + segment.id === selectedSegment.id + ? { + ...segment, + videoUrl: updatedVideos, + video_status: 1, + status: 1 // 更新片段状态为完成 + } + : segment + ) + ); + + } else { + + // 更新 selectedSegment + setSelectedSegment(prev => ({ + ...prev!, + videoUrl: [], + video_status: 2 // 设置为失败状态 + })); + + // 同步更新 videoSegments 中的对应项 + setVideoSegments(prev => + prev.map(segment => + segment.id === selectedSegment!.id + ? { + ...segment, + videoUrl: [], + video_status: 2, + status: 2 // 更新片段状态为失败 + } + : segment + ) + ); + + } + return; + } + + // 如果未完成且未达到最大尝试次数,继续轮询 + if (attempts < maxAttempts && isComponentMounted.current) { + attempts++; + timeoutId = setTimeout(poll, interval); + } else { + setSelectedSegment(prev => ({ + ...prev!, + video_status: 2 // 设置为失败状态 + })); + } + } catch (error) { + setSelectedSegment(prev => ({ + ...prev!, + video_status: 2 // 设置为失败状态 + })); + } + }; + + await poll(); + }, [selectedSegment]); + /** * 重新生成视频片段 * @param shotPrompt 镜头描述数据 @@ -180,8 +269,6 @@ export const useShotService = (): UseShotService => { try { setLoading(true); - console.log('shotInfo-selectedSegment', selectedSegment); - // 调用API重新生成视频片段,返回任务状态信息 const taskResult = await vidoEditUseCase.regenerateVideoSegment( projectId, @@ -198,6 +285,7 @@ export const useShotService = (): UseShotService => { video_ids: [selectedSegment!.id], }, ]); + // 如果重新生成的是现有片段,更新其状态为处理中 (0: 视频加载中) if (selectedSegment) { setVideoSegments((prev) => @@ -205,14 +293,18 @@ export const useShotService = (): UseShotService => { segment.id === selectedSegment.id ? { ...segment, + videoUrl: [], + video_status: 0, status: 0, // 设置为视频加载中状态 } : segment ) ); + + // 开始轮询视频生成状态 + await pollVideoStatus(taskResult.task_id); } - setIntervalIdHandler(projectId); - // 返回当前选中的片段,因为现在API返回的是任务状态而不是完整的片段 + return selectedSegment!; } catch (error) { console.error("重新生成视频片段失败:", error); @@ -220,7 +312,7 @@ export const useShotService = (): UseShotService => { } finally { setLoading(false); } - }, [projectId, selectedSegment, vidoEditUseCase]); + }, [projectId, selectedSegment, vidoEditUseCase, pollVideoStatus]); /** * AI优化视频内容 diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx index 50eb4c4..27b6705 100644 --- a/components/ui/shot-tab-content.tsx +++ b/components/ui/shot-tab-content.tsx @@ -51,8 +51,8 @@ export const ShotTabContent = (props: ShotTabContentProps) => { const [pendingRegeneration, setPendingRegeneration] = useState(false); useEffect(() => { - console.log('shotTabContent-----scriptRoles', scriptRoles); - }, [scriptRoles]); + console.log('shotTabContent-----shotData', shotData); + }, [shotData]); useEffect(() => { if (pendingRegeneration) { @@ -251,6 +251,7 @@ export const ShotTabContent = (props: ShotTabContentProps) => { {shot.status === 1 && shot.videoUrl[0] ? (