diff --git a/src/engine/components/MediaImg.tsx b/src/engine/components/MediaImg.tsx index 46cd273..67c371e 100644 --- a/src/engine/components/MediaImg.tsx +++ b/src/engine/components/MediaImg.tsx @@ -6,24 +6,58 @@ * - Error handling for failed loads * - User-select disabled for composition use */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; interface ImgProps extends React.ImgHTMLAttributes { src: string; } -export const Img: React.FC = ({ style, ...props }) => ( - { - // Silently handle broken images in compositions - const target = e.currentTarget; - target.style.opacity = '0'; - }} - /> -); +export const Img: React.FC = ({ style, onLoad, onError, ...props }) => { + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + if (typeof window !== 'undefined') { + (window as any).__pendingLoads = ((window as any).__pendingLoads || 0) + 1; + } + return () => { + if (typeof window !== 'undefined' && !isLoaded) { + (window as any).__pendingLoads--; + } + }; + }, []); // Run once on mount + + const handleLoad = (e: React.SyntheticEvent) => { + if (!isLoaded) { + setIsLoaded(true); + if (typeof window !== 'undefined') { + (window as any).__pendingLoads--; + } + } + if (onLoad) onLoad(e); + }; + + const handleError = (e: React.SyntheticEvent) => { + if (!isLoaded) { + setIsLoaded(true); + if (typeof window !== 'undefined') { + (window as any).__pendingLoads--; + } + } + const target = e.currentTarget; + target.style.opacity = '0'; + if (onError) onError(e); + }; + + return ( + + ); +}; diff --git a/src/engine/components/MediaVideo.tsx b/src/engine/components/MediaVideo.tsx index c9d00de..e7dc335 100644 --- a/src/engine/components/MediaVideo.tsx +++ b/src/engine/components/MediaVideo.tsx @@ -49,6 +49,39 @@ export const Video: React.FC = ({ const { playing } = usePlayerState(); const videoRef = useRef(null); const wasPlayingRef = useRef(false); + const [isReady, setIsReady] = useState(false); + + // Sync initial load + useEffect(() => { + if (typeof window !== 'undefined') { + (window as any).__pendingLoads = ((window as any).__pendingLoads || 0) + 1; + } + return () => { + if (typeof window !== 'undefined' && !isReady) { + (window as any).__pendingLoads--; + } + }; + }, []); // Run once on mount + + const handleCanPlay = (e: React.SyntheticEvent) => { + if (!isReady) { + setIsReady(true); + if (typeof window !== 'undefined') { + (window as any).__pendingLoads--; + } + } + if (props.onCanPlay) props.onCanPlay(e); + }; + + const handleError = (e: React.SyntheticEvent) => { + if (!isReady) { + setIsReady(true); + if (typeof window !== 'undefined') { + (window as any).__pendingLoads--; + } + } + if (onError) onError(e); + }; // Compute the video's local time const videoFrame = frame + startFrom; @@ -136,7 +169,8 @@ export const Video: React.FC = ({ userSelect: 'none', ...style, }} - onError={onError} + onCanPlay={handleCanPlay} + onError={handleError} /> ); }; diff --git a/src/engine/renderer/puppeteerRenderer.ts b/src/engine/renderer/puppeteerRenderer.ts index ef57ec3..0d34def 100644 --- a/src/engine/renderer/puppeteerRenderer.ts +++ b/src/engine/renderer/puppeteerRenderer.ts @@ -136,7 +136,9 @@ export async function renderFrames(options: RenderFrameOptions): Promise { requestAnimationFrame(() => { const checkVideoSeeks = () => { - if (!(window as any).__pendingSeeks) { + const pendingSeeks = (window as any).__pendingSeeks || 0; + const pendingLoads = (window as any).__pendingLoads || 0; + if (pendingSeeks === 0 && pendingLoads === 0) { resolve(); } else { setTimeout(checkVideoSeeks, 10); diff --git a/src/pages/RenderPage.tsx b/src/pages/RenderPage.tsx index 16a813a..a1b0415 100644 --- a/src/pages/RenderPage.tsx +++ b/src/pages/RenderPage.tsx @@ -56,9 +56,11 @@ export const RenderPage: React.FC = () => { // Update ready state when config changes useEffect(() => { if (config && (window as any).__BRADLY_RENDER__) { - requestAnimationFrame(() => { + document.fonts.ready.then(() => { requestAnimationFrame(() => { - (window as any).__BRADLY_RENDER__.ready = true; + requestAnimationFrame(() => { + (window as any).__BRADLY_RENDER__.ready = true; + }); }); }); }