@@ -159,14 +159,22 @@ export function EffectPreview({
159159 * Определяем использует ли эффект WebGL
160160 */
161161 const useWebGL = useMemo ( ( ) => {
162- return ! ! ( processedEffect ?. processors ?. webgl ?. fragmentShader )
162+ const hasWebGL = ! ! ( processedEffect ?. processors ?. webgl ?. fragmentShader )
163+ if ( processedEffect ) {
164+ void logger . info ( "Effect rendering mode" , {
165+ effectId : processedEffect . id ,
166+ useWebGL : hasWebGL ,
167+ hasFragmentShader : ! ! processedEffect ?. processors ?. webgl ?. fragmentShader ,
168+ } )
169+ }
170+ return hasWebGL
163171 } , [ processedEffect ] )
164172
165173 /**
166- * Применение эффекта к видео (WebGL или CSS )
174+ * Применение CSS- эффекта к видео (только для эффектов без WebGL )
167175 */
168176 useEffect ( ( ) => {
169- if ( ! effect ) return
177+ if ( ! effect || useWebGL ) return // Пропускаем если используем WebGL
170178 if ( ! videoSrc || ! videoRef . current ) return
171179 const videoElement = videoRef . current
172180
@@ -184,72 +192,148 @@ export function EffectPreview({
184192 videoElement . playbackRate = playbackRate
185193 videoElement . loop = true
186194
187- // WebGL рендеринг
188- if ( useWebGL && canvasRef . current ) {
189- const canvas = canvasRef . current
190- canvas . width = width
191- canvas . height = height
192-
193- // Функция рендеринга кадра
194- const renderFrame = async ( ) => {
195- if ( ! videoElement || ! canvas || videoElement . paused || videoElement . ended ) {
196- return
197- }
198-
199- try {
200- await getEffectsPreviewService ( ) . applyEffect (
201- videoElement ,
202- processedEffect ! . id ,
203- effectParams ,
204- canvas ,
205- )
206- } catch ( error ) {
207- void logger . error ( "WebGL effect rendering failed" , { error } )
208- }
209-
210- // Следующий кадр
211- animationFrameRef . current = requestAnimationFrame ( renderFrame )
212- }
195+ // Применяем CSS фильтры
196+ const cssFilter = generateCSSFilterForEffect ( processedEffect || effect , effectParams )
197+ if ( cssFilter ) {
198+ videoElement . style . filter = cssFilter
199+ }
213200
214- // Начинаем рендеринг при hover
215- if ( isHovering && ! videoElement . paused ) {
216- animationFrameRef . current = requestAnimationFrame ( renderFrame )
217- }
201+ // Специальные эффекты через box-shadow
202+ if (
203+ processedEffect ?. id === "vignette" ||
204+ ( processedEffect ?. category === "lighting" && processedEffect ?. id . includes ( "vignette" ) )
205+ ) {
206+ const intensity = customParams ?. intensity ?? effectParams ?. intensity ?? 0.3
207+ const radius = customParams ?. radius ?? effectParams ?. radius ?? 0.8
208+ const shadowSize = Math . round ( Math . min ( width , height ) * ( 1 - radius ) * 0.5 )
209+ const shadowBlur = Math . round ( shadowSize * intensity * 2 )
210+ videoElement . style . boxShadow = `inset 0 0 ${ shadowBlur } px ${ shadowSize } px rgba(0,0,0,${ intensity } )`
211+ } else {
212+ videoElement . style . boxShadow = ""
213+ }
218214
219- return ( ) => {
220- if ( animationFrameRef . current ) {
221- cancelAnimationFrame ( animationFrameRef . current )
222- animationFrameRef . current = null
223- }
224- }
215+ return ( ) => {
216+ videoElement . style . filter = ""
217+ videoElement . style . boxShadow = ""
225218 }
226- // CSS фильтры (fallback)
227- else {
228- const cssFilter = generateCSSFilterForEffect ( processedEffect || effect , effectParams )
229- if ( cssFilter ) {
230- videoElement . style . filter = cssFilter
219+ } , [ processedEffect , width , height , customParams , videoSrc , effect , useWebGL ] )
220+
221+ /**
222+ * Рендеринг первого кадра с WebGL эффектом
223+ */
224+ useEffect ( ( ) => {
225+ if ( ! useWebGL || ! videoRef . current || ! canvasRef . current || ! processedEffect ) return
226+
227+ const videoElement = videoRef . current
228+ const canvas = canvasRef . current
229+
230+ // Устанавливаем размеры canvas
231+ canvas . width = width
232+ canvas . height = height
233+
234+ // Устанавливаем скорость воспроизведения
235+ const playbackRate = getPlaybackRate ( processedEffect )
236+ videoElement . playbackRate = playbackRate
237+ videoElement . loop = true
238+
239+ // Функция рендеринга одного кадра
240+ const renderSingleFrame = async ( ) => {
241+ if ( videoElement . readyState < 2 ) return // Видео ещё не готово
242+
243+ const effectParams : Record < string , any > = { }
244+ if ( processedEffect . parameters ) {
245+ processedEffect . parameters . forEach ( ( param ) => {
246+ effectParams [ param . id ] = param . currentValue ?? param . defaultValue
247+ } )
231248 }
249+ Object . assign ( effectParams , customParams )
250+
251+ try {
252+ await getEffectsPreviewService ( ) . applyEffect (
253+ videoElement ,
254+ processedEffect . id ,
255+ effectParams ,
256+ canvas ,
257+ )
258+ } catch ( error ) {
259+ void logger . error ( "WebGL effect rendering failed" , { error } )
260+ }
261+ }
262+
263+ // Рендерим первый кадр когда видео готово
264+ const handleLoadedData = ( ) => {
265+ void logger . info ( "Rendering first frame with WebGL" , {
266+ effectId : processedEffect . id ,
267+ readyState : videoElement . readyState ,
268+ } )
269+ void renderSingleFrame ( )
270+ }
271+
272+ if ( videoElement . readyState >= 2 ) {
273+ void logger . info ( "Video ready, rendering first frame immediately" , {
274+ effectId : processedEffect . id ,
275+ readyState : videoElement . readyState ,
276+ } )
277+ void renderSingleFrame ( )
278+ } else {
279+ void logger . info ( "Waiting for video to load" , {
280+ effectId : processedEffect . id ,
281+ readyState : videoElement . readyState ,
282+ } )
283+ videoElement . addEventListener ( "loadeddata" , handleLoadedData )
284+ }
285+
286+ return ( ) => {
287+ videoElement . removeEventListener ( "loadeddata" , handleLoadedData )
288+ }
289+ } , [ useWebGL , processedEffect , width , height , customParams , videoSrc ] )
290+
291+ /**
292+ * Непрерывный WebGL рендеринг при hover
293+ */
294+ useEffect ( ( ) => {
295+ if ( ! useWebGL || ! isHovering || ! videoRef . current || ! canvasRef . current || ! processedEffect ) return
296+
297+ const videoElement = videoRef . current
298+ const canvas = canvasRef . current
299+
300+ if ( videoElement . paused || videoElement . ended ) return
301+
302+ const effectParams : Record < string , any > = { }
303+ if ( processedEffect . parameters ) {
304+ processedEffect . parameters . forEach ( ( param ) => {
305+ effectParams [ param . id ] = param . currentValue ?? param . defaultValue
306+ } )
307+ }
308+ Object . assign ( effectParams , customParams )
232309
233- // Специальные эффекты через box-shadow
234- if (
235- processedEffect ?. id === "vignette" ||
236- ( processedEffect ?. category === "lighting" && processedEffect ?. id . includes ( "vignette" ) )
237- ) {
238- const intensity = customParams ?. intensity ?? effectParams ?. intensity ?? 0.3
239- const radius = customParams ?. radius ?? effectParams ?. radius ?? 0.8
240- const shadowSize = Math . round ( Math . min ( width , height ) * ( 1 - radius ) * 0.5 )
241- const shadowBlur = Math . round ( shadowSize * intensity * 2 )
242- videoElement . style . boxShadow = `inset 0 0 ${ shadowBlur } px ${ shadowSize } px rgba(0,0,0,${ intensity } )`
243- } else {
244- videoElement . style . boxShadow = ""
310+ // Функция рендеринга кадра в цикле
311+ const renderFrame = async ( ) => {
312+ if ( videoElement . paused || videoElement . ended ) return
313+
314+ try {
315+ await getEffectsPreviewService ( ) . applyEffect (
316+ videoElement ,
317+ processedEffect . id ,
318+ effectParams ,
319+ canvas ,
320+ )
321+ } catch ( error ) {
322+ void logger . error ( "WebGL continuous rendering failed" , { error } )
245323 }
246324
247- return ( ) => {
248- videoElement . style . filter = ""
249- videoElement . style . boxShadow = ""
325+ animationFrameRef . current = requestAnimationFrame ( renderFrame )
326+ }
327+
328+ animationFrameRef . current = requestAnimationFrame ( renderFrame )
329+
330+ return ( ) => {
331+ if ( animationFrameRef . current ) {
332+ cancelAnimationFrame ( animationFrameRef . current )
333+ animationFrameRef . current = null
250334 }
251335 }
252- } , [ processedEffect , width , height , customParams , videoSrc , effect , useWebGL , isHovering ] )
336+ } , [ useWebGL , isHovering , processedEffect , customParams , videoSrc ] )
253337
254338 /**
255339 * Управление воспроизведением при наведении
@@ -268,30 +352,8 @@ export function EffectPreview({
268352 // Без наведения - останавливаем и возвращаем на первый кадр
269353 videoElement . pause ( )
270354 videoElement . currentTime = 0
271-
272- // Для WebGL эффектов - рендерим первый кадр
273- if ( useWebGL && canvasRef . current && processedEffect ) {
274- const effectParams : Record < string , any > = { }
275- if ( processedEffect . parameters ) {
276- processedEffect . parameters . forEach ( ( param ) => {
277- effectParams [ param . id ] = param . currentValue ?? param . defaultValue
278- } )
279- }
280- Object . assign ( effectParams , customParams )
281-
282- // Рендерим один кадр
283- setTimeout ( ( ) => {
284- if ( canvasRef . current && videoElement . readyState >= 2 ) {
285- getEffectsPreviewService ( )
286- . applyEffect ( videoElement , processedEffect . id , effectParams , canvasRef . current )
287- . catch ( ( err ) => {
288- void logger . error ( "Failed to render first frame" , { error : err } )
289- } )
290- }
291- } , 50 )
292- }
293355 }
294- } , [ isHovering , videoSrc , useWebGL , processedEffect , customParams ] )
356+ } , [ isHovering , videoSrc ] )
295357
296358 return (
297359 < div className = "flex flex-col items-center" >
0 commit comments