forked from 77media/video-flow
处理播放声音
This commit is contained in:
parent
c26b4558c8
commit
b5ff2e7026
@ -63,12 +63,50 @@ export interface ScriptEpisode {
|
||||
|
||||
// 创建剧集
|
||||
export const createScriptEpisode = async (data: CreateScriptEpisodeRequest): Promise<ApiResponse<ScriptEpisode>> => {
|
||||
return post<ApiResponse<ScriptEpisode>>('/script_episode/create', data);
|
||||
// return post<ApiResponse<ScriptEpisode>>('/script_episode/create', data);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
code: 0,
|
||||
message: 'success',
|
||||
data: {
|
||||
id: 1,
|
||||
title: 'test',
|
||||
script_id: 1,
|
||||
status: 1,
|
||||
episode_sort: 1,
|
||||
creator_name: 'king',
|
||||
created_at: '2025-07-03 10:00:00',
|
||||
updated_at: '2025-07-03 10:00:00'
|
||||
},
|
||||
successful: true
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
// 更新剧集
|
||||
export const updateScriptEpisode = async (data: UpdateScriptEpisodeRequest): Promise<ApiResponse<ScriptEpisode>> => {
|
||||
return post<ApiResponse<ScriptEpisode>>('/script_episode/update', data);
|
||||
// return post<ApiResponse<ScriptEpisode>>('/script_episode/update', data);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
code: 0,
|
||||
message: 'success',
|
||||
data: {
|
||||
id: 1,
|
||||
title: 'test',
|
||||
script_id: 1,
|
||||
status: 1,
|
||||
episode_sort: 1,
|
||||
creator_name: 'king',
|
||||
created_at: '2025-07-03 10:00:00',
|
||||
updated_at: '2025-07-03 10:00:00'
|
||||
},
|
||||
successful: true
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
// 获取剧集详情
|
||||
|
||||
@ -72,7 +72,31 @@ export interface DeleteScriptProjectRequest {
|
||||
|
||||
// 创建剧本项目
|
||||
export const createScriptProject = async (data: CreateScriptProjectRequest): Promise<ApiResponse<ScriptProject>> => {
|
||||
return post<ApiResponse<ScriptProject>>('/script_project/create', data);
|
||||
// return post<ApiResponse<ScriptProject>>('/script_project/create', data);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
code: 0,
|
||||
message: 'success',
|
||||
data: {
|
||||
id: 1,
|
||||
title: '2025·07·03·001',
|
||||
script_author: 'king',
|
||||
characters: [],
|
||||
summary: '',
|
||||
project_type: 1,
|
||||
status: 1,
|
||||
cate_tags: [],
|
||||
creator_name: 'king',
|
||||
mode: 1,
|
||||
resolution: 1,
|
||||
created_at: '2025-07-03 10:00:00',
|
||||
updated_at: '2025-07-03 10:00:00'
|
||||
},
|
||||
successful: true
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
// 获取剧本项目列表
|
||||
|
||||
@ -60,7 +60,25 @@ export type ConvertScenePromptResponse = ApiResponse<ScenePrompts>;
|
||||
export const convertScenePrompt = async (
|
||||
request: ConvertScenePromptRequest
|
||||
): Promise<ConvertScenePromptResponse> => {
|
||||
return post<ConvertScenePromptResponse>('/video_flow/convert-scene-prompts', request);
|
||||
// return post<ConvertScenePromptResponse>('/video_flow/convert-scene-prompts', request);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
code: 0,
|
||||
message: 'success',
|
||||
data: {
|
||||
scenes: [],
|
||||
characters: [],
|
||||
summary: '',
|
||||
scene: '',
|
||||
atmosphere: '',
|
||||
episode_id: 0,
|
||||
total_shots: ''
|
||||
},
|
||||
successful: true
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'; // Add this to ensure it's a client component
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Edit2, Trash2, Play, Volume2, VolumeX } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import dynamic from 'next/dynamic';
|
||||
@ -20,6 +20,7 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
||||
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
||||
const [isPlaying, setIsPlaying] = useState<{ [key: string]: boolean }>({});
|
||||
const [isMuted, setIsMuted] = useState<{ [key: string]: boolean }>({});
|
||||
const videoRefs = useRef<{ [key: string]: HTMLVideoElement | null }>({});
|
||||
|
||||
const handleMouseEnter = (id: string) => {
|
||||
setHoveredId(id);
|
||||
@ -28,7 +29,7 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
||||
const handleMouseLeave = (id: string) => {
|
||||
setHoveredId(null);
|
||||
// 暂停视频并重新静音以便下次预览
|
||||
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
|
||||
const video = videoRefs.current[id];
|
||||
if (video) {
|
||||
video.pause();
|
||||
video.muted = true;
|
||||
@ -38,7 +39,7 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
||||
};
|
||||
|
||||
const togglePlay = (id: string) => {
|
||||
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
|
||||
const video = videoRefs.current[id];
|
||||
if (video) {
|
||||
if (video.paused) {
|
||||
// 在用户主动播放时取消静音
|
||||
@ -55,7 +56,7 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
||||
|
||||
const toggleMute = (id: string, event: React.MouseEvent) => {
|
||||
event.stopPropagation(); // 防止触发父元素的点击事件
|
||||
const video = document.getElementById(`video-${id}`) as HTMLVideoElement;
|
||||
const video = videoRefs.current[id];
|
||||
if (video) {
|
||||
video.muted = !video.muted;
|
||||
setIsMuted(prev => ({ ...prev, [id]: video.muted }));
|
||||
@ -74,7 +75,7 @@ function VideoGridLayoutComponent({ videos, onEdit, onDelete }: VideoGridLayoutP
|
||||
{/* 视频容器 */}
|
||||
<div className="relative aspect-video">
|
||||
<video
|
||||
id={`video-${video.id}`}
|
||||
ref={(el) => (videoRefs.current[video.id] = el)}
|
||||
src={video.url}
|
||||
suppressHydrationWarning
|
||||
className="w-full h-full object-cover"
|
||||
|
||||
@ -18,14 +18,12 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const [isMuted, setIsMuted] = useState(true); // 默认静音
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
|
||||
|
||||
// 初始化时同步第一个视频的状态
|
||||
// 确保视频refs数组长度与videos数组一致
|
||||
useEffect(() => {
|
||||
const currentVideo = document.getElementById(`video-screen-${currentIndex}`) as HTMLVideoElement;
|
||||
if (currentVideo) {
|
||||
setIsMuted(currentVideo.muted);
|
||||
}
|
||||
}, [currentIndex]);
|
||||
videoRefs.current = videoRefs.current.slice(0, videos.length);
|
||||
}, [videos.length]);
|
||||
|
||||
// 计算每个面板的样式
|
||||
const getPanelStyle = (index: number) => {
|
||||
@ -55,7 +53,7 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
||||
|
||||
// 切换静音状态
|
||||
const toggleMute = () => {
|
||||
const currentVideo = document.getElementById(`video-screen-${currentIndex}`) as HTMLVideoElement;
|
||||
const currentVideo = videoRefs.current[currentIndex];
|
||||
if (currentVideo) {
|
||||
currentVideo.muted = !currentVideo.muted;
|
||||
setIsMuted(currentVideo.muted);
|
||||
@ -77,7 +75,7 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
||||
setTimeout(() => {
|
||||
setIsAnimating(false);
|
||||
// 同步新视频的静音状态到UI
|
||||
const newVideo = document.getElementById(`video-screen-${newIndex}`) as HTMLVideoElement;
|
||||
const newVideo = videoRefs.current[newIndex];
|
||||
if (newVideo) {
|
||||
setIsMuted(newVideo.muted);
|
||||
}
|
||||
@ -104,7 +102,7 @@ function VideoScreenLayoutComponent({ videos }: VideoScreenLayoutProps) {
|
||||
<div className="relative w-full h-full overflow-hidden rounded-lg">
|
||||
{/* 视频 - Add suppressHydrationWarning to prevent className mismatch warnings */}
|
||||
<video
|
||||
id={`video-screen-${index}`}
|
||||
ref={(el) => (videoRefs.current[index] = el)}
|
||||
src={video.url}
|
||||
suppressHydrationWarning
|
||||
className="w-full h-full object-cover"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user