fixed transition smoothing issue for presentations
This commit is contained in:
parent
7f3b1795af
commit
c25e7cdcc2
@ -24,7 +24,7 @@ import LayoutGuest from '../layouts/Guest';
|
||||
import { getPageTitle } from '../config';
|
||||
import { PRELOAD_CONFIG } from '../config/preload.config';
|
||||
import { usePreloadOrchestrator } from '../hooks/usePreloadOrchestrator';
|
||||
import { useReversePlayback } from '../hooks/useReversePlayback';
|
||||
import { useTransitionPlayback } from '../hooks/useTransitionPlayback';
|
||||
import { logger } from '../lib/logger';
|
||||
import { resolveAssetPlaybackUrl } from '../lib/assetUrl';
|
||||
import { buildElementStyle } from '../lib/elementStyles';
|
||||
@ -32,7 +32,6 @@ import type {
|
||||
RuntimeProject,
|
||||
RuntimePage,
|
||||
RuntimePageLink,
|
||||
TransitionOverlayState,
|
||||
} from '../types/runtime';
|
||||
|
||||
interface RuntimePresentationProps {
|
||||
@ -133,13 +132,16 @@ export default function RuntimePresentation({
|
||||
const [pageLinks, setPageLinks] = useState<RuntimePageLink[]>([]);
|
||||
const [selectedPageId, setSelectedPageId] = useState<string | null>(null);
|
||||
const [pageHistory, setPageHistory] = useState<string[]>([]);
|
||||
const [overlayTransition, setOverlayTransition] =
|
||||
useState<TransitionOverlayState | null>(null);
|
||||
const [transitionPreview, setTransitionPreview] = useState<{
|
||||
targetPageId: string;
|
||||
videoUrl: string;
|
||||
isReverse: boolean;
|
||||
} | null>(null);
|
||||
const [error, setError] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
const overlayVideoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const transitionVideoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
// API request config with custom headers for project/environment
|
||||
const apiConfig = useMemo(
|
||||
@ -212,6 +214,50 @@ export default function RuntimePresentation({
|
||||
enabled: !isLoading && !error,
|
||||
});
|
||||
|
||||
// Integrate useTransitionPlayback hook for smooth transitions (matches Constructor pattern)
|
||||
const { isBuffering } = useTransitionPlayback({
|
||||
videoRef: transitionVideoRef,
|
||||
transition: transitionPreview
|
||||
? {
|
||||
videoUrl: transitionPreview.videoUrl,
|
||||
reverseMode: transitionPreview.isReverse ? 'reverse' : 'none',
|
||||
targetPageId: transitionPreview.targetPageId,
|
||||
displayName: 'Transition',
|
||||
}
|
||||
: null,
|
||||
onComplete: (targetPageId) => {
|
||||
if (targetPageId) {
|
||||
const targetPage = pages.find((p) => p.id === targetPageId);
|
||||
waitForPageImages(targetPage || null).then(() => {
|
||||
setSelectedPageId(targetPageId);
|
||||
setPageHistory((prev) => [...prev, targetPageId]);
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setTransitionPreview(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
features: {
|
||||
useBlobUrl: true,
|
||||
preDecodeImages: true,
|
||||
getTargetPageImages: () => {
|
||||
if (!transitionPreview?.targetPageId) return [];
|
||||
const targetPage = pages.find(
|
||||
(p) => p.id === transitionPreview.targetPageId,
|
||||
);
|
||||
if (!targetPage?.background_image_url) return [];
|
||||
const url = resolveAssetPlaybackUrl(targetPage.background_image_url);
|
||||
return url ? [url] : [];
|
||||
},
|
||||
},
|
||||
preload: {
|
||||
preloadedUrls: preloadOrchestrator?.preloadedUrls || new Set(),
|
||||
getCachedBlobUrl: preloadOrchestrator?.getCachedBlobUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const toggleFullscreen = useCallback(async () => {
|
||||
try {
|
||||
if (!document.fullscreenElement) {
|
||||
@ -361,10 +407,9 @@ export default function RuntimePresentation({
|
||||
if (!targetPage) return;
|
||||
|
||||
if (transitionVideoUrl) {
|
||||
// Play transition (forward or reverse)
|
||||
setOverlayTransition({
|
||||
// Play transition using useTransitionPlayback hook
|
||||
setTransitionPreview({
|
||||
targetPageId,
|
||||
transitionName: 'Transition',
|
||||
videoUrl: resolveAssetPlaybackUrl(transitionVideoUrl),
|
||||
isReverse: isBack,
|
||||
});
|
||||
@ -391,67 +436,6 @@ export default function RuntimePresentation({
|
||||
[navigateToPage],
|
||||
);
|
||||
|
||||
const finishOverlayTransition = useCallback(async () => {
|
||||
if (!overlayTransition) return;
|
||||
|
||||
const targetPage = pages.find(
|
||||
(p) => p.id === overlayTransition.targetPageId,
|
||||
);
|
||||
|
||||
// Wait for images while showing last frame
|
||||
await waitForPageImages(targetPage || null);
|
||||
|
||||
// Switch page
|
||||
setSelectedPageId(overlayTransition.targetPageId);
|
||||
setPageHistory((prev) => [...prev, overlayTransition.targetPageId]);
|
||||
|
||||
// Hide transition after React renders
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setOverlayTransition(null);
|
||||
});
|
||||
});
|
||||
}, [overlayTransition, pages]);
|
||||
|
||||
// Use the reverse playback hook (same as constructor.tsx)
|
||||
const { startReverse, stopReverse } = useReversePlayback({
|
||||
videoRef: overlayVideoRef,
|
||||
onComplete: finishOverlayTransition,
|
||||
preloadedUrls: preloadOrchestrator.preloadedUrls,
|
||||
videoUrl: overlayTransition?.videoUrl,
|
||||
getCachedBlobUrl: preloadOrchestrator.getCachedBlobUrl,
|
||||
});
|
||||
|
||||
// Handle reverse playback when transition starts in reverse mode
|
||||
useEffect(() => {
|
||||
if (!overlayTransition?.isReverse) return;
|
||||
|
||||
const video = overlayVideoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
// Start reverse when video data is ready (preloaded assets load instantly)
|
||||
const handleLoadedData = () => {
|
||||
startReverse();
|
||||
};
|
||||
|
||||
if (video.readyState >= 2) {
|
||||
handleLoadedData();
|
||||
} else {
|
||||
video.addEventListener('loadeddata', handleLoadedData, { once: true });
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopReverse();
|
||||
video.removeEventListener('loadeddata', handleLoadedData);
|
||||
};
|
||||
}, [
|
||||
overlayTransition?.isReverse,
|
||||
overlayTransition?.videoUrl,
|
||||
overlayTransition?.durationSec,
|
||||
startReverse,
|
||||
stopReverse,
|
||||
]);
|
||||
|
||||
// Render element content based on type
|
||||
const renderElementContent = (element: any) => {
|
||||
// Navigation buttons
|
||||
@ -769,23 +753,17 @@ export default function RuntimePresentation({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Transition overlay */}
|
||||
{overlayTransition && (
|
||||
<div className='absolute inset-0 z-40 bg-black'>
|
||||
{/* Transition overlay - uses useTransitionPlayback hook for smooth transitions */}
|
||||
{transitionPreview && (
|
||||
<div className='fixed inset-0 z-50 overflow-hidden pointer-events-none'>
|
||||
<video
|
||||
ref={overlayVideoRef}
|
||||
className='w-full h-full object-cover'
|
||||
src={overlayTransition.videoUrl}
|
||||
autoPlay={!overlayTransition.isReverse}
|
||||
ref={transitionVideoRef}
|
||||
className='absolute inset-0 h-full w-full object-cover transition-opacity duration-300 ease-linear'
|
||||
style={{ opacity: isBuffering ? 0 : 1 }}
|
||||
muted
|
||||
playsInline
|
||||
preload='auto'
|
||||
onEnded={
|
||||
overlayTransition.isReverse
|
||||
? undefined
|
||||
: finishOverlayTransition
|
||||
}
|
||||
onError={finishOverlayTransition}
|
||||
disablePictureInPicture
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user