import { forwardRef, useEffect, useRef, useState } from "react"; import { isLottieAnimationLoaded } from "@hyperframes/core/runtime/lottie-readiness"; import { useMountEffect } from "../../hooks/useMountEffect"; import { HyperframesLoader } from "../../components/ui"; // NOTE: importing "@hyperframes/player" registers a class extending HTMLElement // at module load, which throws under SSR. Defer the import to the mount effect // so it only runs in the browser. interface PlayerProps { projectId?: string; directUrl?: string; onLoad: () => void; onCompositionLoadingChange?: (loading: boolean) => void; portrait?: boolean; style?: React.CSSProperties; suppressLoadingOverlay?: boolean; } interface HyperframesPlayerElement extends HTMLElement { iframeElement: HTMLIFrameElement; } const MEDIA_HAVE_FUTURE_DATA = 3; const MEDIA_NETWORK_NO_SOURCE = 3; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function getShaderTransitionLoading(event: Event): boolean | null { if (!(event instanceof CustomEvent)) return null; const detail: unknown = event.detail; if (!isRecord(detail)) return null; const state = detail.state; if (!isRecord(state)) return null; return state.loading === true && state.ready !== true; } const COMPOSITION_LOADING_OVERLAY_DELAY_MS = 400; export function shouldShowCompositionLoadingOverlay(compositionLoading: boolean): boolean { return compositionLoading; } function enableInteractiveIframe(player: HyperframesPlayerElement): void { const root = player.shadowRoot; if (!root) return; const container = root.querySelector(".hfp-container"); const iframe = root.querySelector(".hfp-iframe"); container?.style.setProperty("pointer-events", "auto"); iframe?.style.setProperty("pointer-events", "auto"); } function isPreviewMediaElement(el: Element): el is HTMLMediaElement { const tagName = el.tagName.toLowerCase(); return tagName === "video" || tagName === "audio"; } // Assets are considered ready when every `