fix: Wait for images, videos, and fonts to load before capturing frame 0 to fix thumbnails
This commit is contained in:
@@ -6,13 +6,49 @@
|
||||
* - 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<HTMLImageElement> {
|
||||
src: string;
|
||||
}
|
||||
|
||||
export const Img: React.FC<ImgProps> = ({ style, ...props }) => (
|
||||
export const Img: React.FC<ImgProps> = ({ 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<HTMLImageElement>) => {
|
||||
if (!isLoaded) {
|
||||
setIsLoaded(true);
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).__pendingLoads--;
|
||||
}
|
||||
}
|
||||
if (onLoad) onLoad(e);
|
||||
};
|
||||
|
||||
const handleError = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
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 (
|
||||
<img
|
||||
draggable={false}
|
||||
{...props}
|
||||
@@ -20,10 +56,8 @@ export const Img: React.FC<ImgProps> = ({ style, ...props }) => (
|
||||
userSelect: 'none',
|
||||
...style,
|
||||
}}
|
||||
onError={(e) => {
|
||||
// Silently handle broken images in compositions
|
||||
const target = e.currentTarget;
|
||||
target.style.opacity = '0';
|
||||
}}
|
||||
onLoad={handleLoad}
|
||||
onError={handleError}
|
||||
/>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
@@ -49,6 +49,39 @@ export const Video: React.FC<VideoProps> = ({
|
||||
const { playing } = usePlayerState();
|
||||
const videoRef = useRef<HTMLVideoElement>(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<HTMLVideoElement, Event>) => {
|
||||
if (!isReady) {
|
||||
setIsReady(true);
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).__pendingLoads--;
|
||||
}
|
||||
}
|
||||
if (props.onCanPlay) props.onCanPlay(e);
|
||||
};
|
||||
|
||||
const handleError = (e: React.SyntheticEvent<HTMLVideoElement, Event>) => {
|
||||
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<VideoProps> = ({
|
||||
userSelect: 'none',
|
||||
...style,
|
||||
}}
|
||||
onError={onError}
|
||||
onCanPlay={handleCanPlay}
|
||||
onError={handleError}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -136,7 +136,9 @@ export async function renderFrames(options: RenderFrameOptions): Promise<string[
|
||||
requestAnimationFrame(() => {
|
||||
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);
|
||||
|
||||
@@ -56,11 +56,13 @@ export const RenderPage: React.FC = () => {
|
||||
// Update ready state when config changes
|
||||
useEffect(() => {
|
||||
if (config && (window as any).__BRADLY_RENDER__) {
|
||||
document.fonts.ready.then(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
(window as any).__BRADLY_RENDER__.ready = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user